from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth import logout, authenticate, login
from django.db.models import Sum, F, ExpressionWrapper, DecimalField, Max
from django.db import models
from django.db.models.functions import Coalesce
from django.core.paginator import Paginator
from django.http import JsonResponse, HttpResponse, FileResponse, Http404
from django.conf import settings
from django.views.decorators.http import require_POST
from django.db import transaction
from django.utils import timezone
from django.contrib import messages
from io import BytesIO
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, Border, Side, Alignment
from openpyxl.utils import get_column_letter
from openpyxl.drawing.image import Image as ExcelImage
import qrcode
from PIL import Image as PILImage
import os
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from datetime import datetime, timedelta
from django.db.models import Q
from django.forms import formset_factory
from django.contrib.auth.models import Group, Permission, User
from .models import Organization, Department, Location, AssetCategory, Asset, Supplier, Contract, AssetLog, UserProfile, Warehouse, Consumable, InventoryLog, MaintenanceRecord, InventoryPlan, InventoryItem, AssetBorrow, AssetImage, AssetFile, SupplierDocument, AssetLedger, ContractReview, ContractFile
from .forms_contract import ContractForm, ContractFileForm, ContractReviewForm, ContractSearchForm
from .forms_workflow import AssetBorrowRequestForm, WorkflowTemplateForm, AssetScrapRequestForm
from .forms_supplier import SupplierForm, SupplierDocumentForm
from decimal import Decimal

import logging

# Module logger
logger = logging.getLogger(__name__)

# 导入新的视图模块
from .views_system import *
from .views_transfer import *
from .views_api import *
from . import views_label


def get_date_format_sql(field_name, format_type='year'):
    """
    获取数据库兼容的日期格式化SQL
    
    Args:
        field_name: 字段名
        format_type: 格式类型 ('year', 'month', 'date')
    
    Returns:
        SQL格式化字符串
    """
    from django.db import connection
    
    if connection.vendor == 'mysql':
        if format_type == 'year':
            return f"DATE_FORMAT({field_name}, '%%Y')"
        elif format_type == 'month':
            return f"DATE_FORMAT({field_name}, '%%Y-%%m')"
        elif format_type == 'date':
            return f"DATE_FORMAT({field_name}, '%%Y-%%m-%%d')"
    else:
        # SQLite 或其他数据库
        if format_type == 'year':
            return f"strftime('%%Y', {field_name})"
        elif format_type == 'month':
            return f"strftime('%%Y-%%m', {field_name})"
        elif format_type == 'date':
            return f"strftime('%%Y-%%m-%%d', {field_name})"
    
    return field_name

def user_login(request):
    """用户登录视图"""
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        
        if user is not None:
            login(request, user)
            # 重定向到用户原本想要访问的页面，或者首页
            next_page = request.GET.get('next', 'assets:index')
            return redirect(next_page)
        else:
            messages.error(request, '用户名或密码错误')
    
    return render(request, 'assets/login.html')


def user_logout(request):
    """用户登出视图"""
    logout(request)
    messages.success(request, '您已成功退出登录')
    return redirect('assets:login')


@login_required
@login_required
def index(request):
    """首页视图"""
    from django.db.models import Count, F
    from django.contrib.auth.models import User
    
    try:
        # 获取基础统计数据
        total_assets = Asset.objects.count()
        active_users = User.objects.filter(is_active=True).count()
        departments_count = Department.objects.count()
        suppliers_count = Supplier.objects.count()
        
        # 资产状态统计
        asset_status_stats = Asset.objects.values('status').annotate(count=Count('id'))
        status_data = {}
        for stat in asset_status_stats:
            status_data[stat['status']] = stat['count']
        
        # 最近活动 - 简化查询
        recent_assets = Asset.objects.select_related('category', 'department').order_by('-created_at')[:5]
        
        # 维修记录 - 添加异常处理
        try:
            recent_maintenance = MaintenanceRecord.objects.select_related('asset').order_by('-created_at')[:5]
        except:
            recent_maintenance = []
        
        # 库存预警 - 添加异常处理
        try:
            low_stock_consumables = Consumable.objects.filter(
                quantity__lte=F('min_quantity')
            ).count()
        except:
            low_stock_consumables = 0
        
        # 本月统计
        from django.utils import timezone
        current_month = timezone.now().replace(day=1)
        monthly_new_assets = Asset.objects.filter(created_at__gte=current_month).count()
        
        context = {
            'total_assets': total_assets,
            'active_users': active_users,
            'departments_count': departments_count,
            'suppliers_count': suppliers_count,
            'status_data': status_data,
            'recent_assets': recent_assets,
            'recent_maintenance': recent_maintenance,
            'low_stock_consumables': low_stock_consumables,
            'monthly_new_assets': monthly_new_assets,
            'in_use_count': status_data.get('in_use', 0),
            'idle_count': status_data.get('idle', 0),
            'maintenance_count': status_data.get('maintenance', 0),
        }
        
    except Exception as e:
        # 如果出现任何错误，返回默认值
        context = {
            'total_assets': 0,
            'active_users': 0,
            'departments_count': 0,
            'suppliers_count': 0,
            'status_data': {},
            'recent_assets': [],
            'recent_maintenance': [],
            'low_stock_consumables': 0,
            'monthly_new_assets': 0,
            'in_use_count': 0,
            'idle_count': 0,
            'maintenance_count': 0,
        }
    
    return render(request, 'assets/index.html', context)


@login_required
def user_list(request):
    """用户列表视图"""
    # 检查用户是否有查看用户列表的权限
    if not request.user.has_perm('auth.view_user'):
        messages.error(request, '您没有权限查看用户列表')
        return redirect('assets:index')
    
    users = User.objects.all().order_by('-date_joined')
    
    # 搜索功能
    search_query = request.GET.get('search')
    if search_query:
        users = users.filter(
            Q(username__icontains=search_query) | 
            Q(first_name__icontains=search_query) |
            Q(last_name__icontains=search_query) |
            Q(email__icontains=search_query)
        )
    
    # 分页
    paginator = Paginator(users, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    return render(request, 'assets/user_list.html', {
        'page_obj': page_obj,
        'search_query': search_query
    })


@login_required
def user_detail(request, pk):
    """用户详情视图"""
    # 检查用户是否有查看用户详情的权限
    if not request.user.has_perm('auth.view_user'):
        messages.error(request, '您没有权限查看用户详情')
        return redirect('assets:index')
    
    user = get_object_or_404(User, pk=pk)
    groups = Group.objects.all()
    permissions = Permission.objects.all()
    
    return render(request, 'assets/user_detail.html', {
        'user_obj': user,
        'groups': groups,
        'permissions': permissions,
    })


@login_required
def user_create(request):
    """新增用户视图"""
    # 检查用户是否有添加用户的权限
    if not request.user.has_perm('auth.add_user'):
        messages.error(request, '您没有权限新增用户')
        return redirect('assets:index')
    
    groups = Group.objects.all()
    
    if request.method == 'POST':
        # 获取表单数据
        username = request.POST.get('username')
        password = request.POST.get('password')
        password_confirm = request.POST.get('password_confirm')
        first_name = request.POST.get('first_name')
        last_name = request.POST.get('last_name')
        email = request.POST.get('email')
        is_active = request.POST.get('is_active') == 'on'
        is_staff = request.POST.get('is_staff') == 'on'
        is_superuser = request.POST.get('is_superuser') == 'on'
        group_ids = request.POST.getlist('groups')
        
        # 验证表单数据
        errors = []
        
        if not username:
            errors.append('用户名不能为空')
        elif User.objects.filter(username=username).exists():
            errors.append('用户名已存在')
        
        if not password:
            errors.append('密码不能为空')
        elif len(password) < 6:
            errors.append('密码长度不能少于6位')
        elif password != password_confirm:
            errors.append('两次输入的密码不一致')
        
        if email and User.objects.filter(email=email).exists():
            errors.append('邮箱已被使用')
        
        if errors:
            for error in errors:
                messages.error(request, error)
            return render(request, 'assets/user_form.html', {
                'groups': groups,
                'form_data': request.POST,
            })
        
        # 创建用户
        user = User.objects.create_user(
            username=username,
            password=password,
            first_name=first_name,
            last_name=last_name,
            email=email,
            is_active=is_active,
            is_staff=is_staff,
            is_superuser=is_superuser
        )
        
        # 设置用户组
        user.groups.set(group_ids)
        
        messages.success(request, f'用户 {user.username} 创建成功')
        return redirect('assets:user_detail', pk=user.pk)
    
    return render(request, 'assets/user_form.html', {
        'groups': groups,
    })


@login_required
def user_edit(request, pk):
    """编辑用户视图"""
    # 检查用户是否有修改用户的权限
    if not request.user.has_perm('auth.change_user'):
        messages.error(request, '您没有权限编辑用户')
        return redirect('assets:index')
    
    user = get_object_or_404(User, pk=pk)
    groups = Group.objects.all()
    
    if request.method == 'POST':
        # 获取表单数据
        first_name = request.POST.get('first_name')
        last_name = request.POST.get('last_name')
        email = request.POST.get('email')
        is_active = request.POST.get('is_active') == 'on'
        is_staff = request.POST.get('is_staff') == 'on'
        is_superuser = request.POST.get('is_superuser') == 'on'
        group_ids = request.POST.getlist('groups')
        
        # 验证邮箱唯一性（排除当前用户）
        if email and User.objects.filter(email=email).exclude(pk=pk).exists():
            messages.error(request, '邮箱已被其他用户使用')
            return render(request, 'assets/user_form.html', {
                'user_obj': user,
                'groups': groups,
            })
        
        # 更新用户信息
        user.first_name = first_name
        user.last_name = last_name
        user.email = email
        user.is_active = is_active
        user.is_staff = is_staff
        user.is_superuser = is_superuser
        user.save()
        
        # 更新用户组
        user.groups.set(group_ids)
        
        messages.success(request, f'用户 {user.username} 更新成功')
        return redirect('assets:user_detail', pk=user.pk)
    
    return render(request, 'assets/user_form.html', {
        'user_obj': user,
        'groups': groups,
    })


@login_required
def user_delete(request, pk):
    """删除用户视图"""
    # 检查用户是否有删除用户的权限
    if not request.user.has_perm('auth.delete_user'):
        messages.error(request, '您没有权限删除用户')
        return redirect('assets:index')
    
    user = get_object_or_404(User, pk=pk)
    
    # 防止删除自己
    if user == request.user:
        messages.error(request, '不能删除自己的账户')
        return redirect('assets:user_list')
    
    # 防止删除超级用户（除非当前用户也是超级用户）
    if user.is_superuser and not request.user.is_superuser:
        messages.error(request, '只有超级用户才能删除超级用户账户')
        return redirect('assets:user_list')
    
    if request.method == 'POST':
        username = user.username
        user.delete()
        messages.success(request, f'用户 {username} 删除成功')
        return redirect('assets:user_list')
    
    return render(request, 'assets/user_confirm_delete.html', {
        'user_obj': user
    })


@login_required
def user_reset_password(request, pk):
    """重置用户密码视图"""
    # 检查用户是否有修改用户的权限
    if not request.user.has_perm('auth.change_user'):
        messages.error(request, '您没有权限重置用户密码')
        return redirect('assets:index')
    
    user = get_object_or_404(User, pk=pk)
    
    if request.method == 'POST':
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        
        # 验证密码
        if not new_password:
            messages.error(request, '新密码不能为空')
            return render(request, 'assets/user_reset_password.html', {'user_obj': user})
        
        if len(new_password) < 6:
            messages.error(request, '密码长度不能少于6位')
            return render(request, 'assets/user_reset_password.html', {'user_obj': user})
        
        if new_password != confirm_password:
            messages.error(request, '两次输入的密码不一致')
            return render(request, 'assets/user_reset_password.html', {'user_obj': user})
        
        # 重置密码
        user.set_password(new_password)
        user.save()
        
        messages.success(request, f'用户 {user.username} 的密码重置成功')
        return redirect('assets:user_detail', pk=user.pk)
    
    return render(request, 'assets/user_reset_password.html', {
        'user_obj': user
    })


@login_required
def user_batch_delete(request):
    """批量删除用户视图"""
    # 检查用户是否有删除用户的权限
    if not request.user.has_perm('auth.delete_user'):
        messages.error(request, '您没有权限删除用户')
        return redirect('assets:index')
    
    if request.method == 'POST':
        user_ids = request.POST.getlist('user_ids')
        
        if not user_ids:
            messages.error(request, '请选择要删除的用户')
            return redirect('assets:user_list')
        
        # 获取要删除的用户
        users_to_delete = User.objects.filter(id__in=user_ids)
        
        # 验证删除权限
        errors = []
        valid_users = []
        
        for user in users_to_delete:
            # 防止删除自己
            if user == request.user:
                errors.append(f'不能删除自己的账户 ({user.username})')
                continue
            
            # 防止删除超级用户（除非当前用户也是超级用户）
            if user.is_superuser and not request.user.is_superuser:
                errors.append(f'只有超级用户才能删除超级用户账户 ({user.username})')
                continue
            
            valid_users.append(user)
        
        # 显示错误信息
        for error in errors:
            messages.error(request, error)
        
        # 删除有效的用户
        if valid_users:
            deleted_count = len(valid_users)
            usernames = [user.username for user in valid_users]
            
            for user in valid_users:
                user.delete()
            
            messages.success(request, f'成功删除 {deleted_count} 个用户: {", ".join(usernames)}')
        
        return redirect('assets:user_list')
    
    return redirect('assets:user_list')


@login_required
def user_toggle_status(request, pk):
    """切换用户状态视图"""
    # 检查用户是否有修改用户的权限
    if not request.user.has_perm('auth.change_user'):
        return JsonResponse({'success': False, 'error': '您没有权限修改用户状态'})
    
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': '请求方法不正确'})
    
    try:
        user = get_object_or_404(User, pk=pk)
        
        # 不能停用自己的账户
        if user == request.user:
            return JsonResponse({'success': False, 'error': '不能停用自己的账户'})
        
        # 获取要设置的状态
        activate = request.POST.get('activate', 'false').lower() == 'true'
        
        # 更新用户状态
        user.is_active = activate
        user.save()
        
        # 记录操作日志（这里可以考虑创建专门的用户操作日志，暂时先注释掉）
        action_desc = '激活' if activate else '停用'
        # TODO: 创建用户操作日志模型
        # UserOperationLog.objects.create(
        #     action='status_change',
        #     description=f'{action_desc}用户账户：{user.username}',
        #     operator=request.user,
        #     target_user=user,
        #     old_value={'is_active': not activate},
        #     new_value={'is_active': activate}
        # )
        
        return JsonResponse({
            'success': True, 
            'message': f'用户 {user.username} 已{action_desc}',
            'is_active': user.is_active
        })
        
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)})


@login_required
def bulk_import_users(request):
    """批量导入用户视图"""
    # 检查用户是否有添加用户的权限
    if not request.user.has_perm('auth.add_user'):
        messages.error(request, '您没有权限导入用户')
        return redirect('assets:index')
    
    groups = Group.objects.all()
    
    if request.method == 'POST' and request.FILES.get('excel_file'):
        excel_file = request.FILES['excel_file']
        
        # 检查文件类型
        if not excel_file.name.endswith(('.xlsx', '.xls')):
            messages.error(request, '请上传Excel文件(.xlsx或.xls)')
            return render(request, 'assets/bulk_import.html', {
                'import_type': 'users',
                'title': '批量导入用户',
                'groups': groups
            })
        
        try:
            # 读取Excel文件
            workbook = load_workbook(filename=BytesIO(excel_file.read()))
            worksheet = workbook.active
            
            # 检查文件结构
            if worksheet.max_row < 2:
                messages.error(request, 'Excel文件没有数据行，请确保第一行是表头，数据从第二行开始')
                return render(request, 'assets/bulk_import.html', {
                    'import_type': 'users',
                    'title': '批量导入用户',
                    'groups': groups
                })
            
            # 预加载数据
            existing_usernames = set(User.objects.values_list('username', flat=True))
            existing_emails = set(User.objects.filter(email__isnull=False).values_list('email', flat=True))
            group_dict = {group.name: group for group in Group.objects.all()}
            
            # 批量导入处理
            success_count = 0
        except Exception:
            # 若运行环境缺少 openpyxl，仍回退到静态模板
            pass
        else:
            # 解析完成（此处为示例结束分支，无需下载模板）
            pass

    # GET 或非上传错误时渲染页面
    return render(request, 'assets/bulk_import.html', {
        'import_type': 'users',
        'title': '批量导入用户',
        'groups': groups
    })

@login_required
@permission_required('auth.view_group', raise_exception=True)
def group_list(request):
    """用户组列表"""
    query = request.GET.get('q', '').strip()
    groups_qs = Group.objects.all()
    if query:
        groups_qs = groups_qs.filter(name__icontains=query)

    paginator = Paginator(groups_qs.order_by('name'), 20)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'assets/group_list.html', {
        'page_obj': page_obj,
        'query': query,
    })


@login_required
@permission_required('auth.add_group', raise_exception=True)
def group_create(request):
    """创建用户组"""
    all_permissions = Permission.objects.select_related('content_type').all().order_by('content_type__app_label', 'codename')

    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        perm_ids = request.POST.getlist('permissions')
        if not name:
            messages.error(request, '组名不能为空')
        else:
            if Group.objects.filter(name=name).exists():
                messages.error(request, '该组名已存在')
            else:
                group = Group.objects.create(name=name)
                if perm_ids:
                    perms = Permission.objects.filter(id__in=perm_ids)
                    group.permissions.set(perms)
                messages.success(request, f'用户组 {name} 创建成功')
                return redirect('assets:group_list')

    return render(request, 'assets/group_form.html', {
        'all_permissions': all_permissions,
        'form_action': 'create',
    })


@login_required
@permission_required('auth.view_group', raise_exception=True)
def group_detail(request, pk):
    """用户组详情"""
    group = get_object_or_404(Group, pk=pk)
    users = User.objects.filter(groups=group).order_by('username')
    permissions = group.permissions.select_related('content_type').order_by('content_type__app_label', 'codename')
    return render(request, 'assets/group_detail.html', {
        'group': group,
        'users': users,
        'permissions': permissions,
    })


@login_required
@permission_required('auth.change_group', raise_exception=True)
def group_edit(request, pk):
    """编辑用户组"""
    group = get_object_or_404(Group, pk=pk)
    all_permissions = Permission.objects.select_related('content_type').all().order_by('content_type__app_label', 'codename')

    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        perm_ids = request.POST.getlist('permissions')
        if not name:
            messages.error(request, '组名不能为空')
        else:
            if Group.objects.exclude(pk=group.pk).filter(name=name).exists():
                messages.error(request, '该组名已被其他组使用')
            else:
                group.name = name
                group.save()
                perms = Permission.objects.filter(id__in=perm_ids)
                group.permissions.set(perms)
                messages.success(request, '用户组更新成功')
                return redirect('assets:group_detail', pk=group.pk)

    selected_perm_ids = set(group.permissions.values_list('id', flat=True))
    return render(request, 'assets/group_form.html', {
        'group': group,
        'all_permissions': all_permissions,
        'selected_perm_ids': selected_perm_ids,
        'form_action': 'edit',
    })


@login_required
@permission_required('auth.delete_group', raise_exception=True)
def group_delete(request, pk):
    """删除用户组"""
    group = get_object_or_404(Group, pk=pk)
    if request.method == 'POST':
        name = group.name
        group.delete()
        messages.success(request, f'用户组 {name} 已删除')
        return redirect('assets:group_list')
    return render(request, 'assets/group_confirm_delete.html', {'group': group})

@login_required
@permission_required('assets.view_organization', raise_exception=True)
def organization_list(request):
    """组织列表"""
    q = request.GET.get('q', '').strip()
    orgs = Organization.objects.all()
    if q:
        orgs = orgs.filter(Q(name__icontains=q) | Q(code__icontains=q))
    paginator = Paginator(orgs.order_by('name'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/organization_list.html', {
        'page_obj': page_obj,
        'query': q,
    })


@login_required
@permission_required('assets.add_organization', raise_exception=True)
def organization_create(request):
    """创建组织"""
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        description = request.POST.get('description', '').strip()
        parent_id = request.POST.get('parent')
        if not name:
            messages.error(request, '组织名称不能为空')
        else:
            if code and Organization.objects.filter(code=code).exists():
                messages.error(request, '组织编码已存在')
            else:
                parent = Organization.objects.filter(pk=parent_id).first() if parent_id else None
                org = Organization.objects.create(name=name, code=code or None, description=description, parent=parent)
                messages.success(request, f'组织 {org.name} 创建成功')
                return redirect('assets:organization_list')
    return render(request, 'assets/organization_form.html', {
        'organizations': Organization.objects.all(),
        'form_action': 'create',
    })


@login_required
@permission_required('assets.view_organization', raise_exception=True)
def organization_detail(request, pk):
    """组织详情"""
    org = get_object_or_404(Organization, pk=pk)
    children = Organization.objects.filter(parent=org)
    departments = Department.objects.filter(organization=org)
    return render(request, 'assets/organization_detail.html', {
        'organization': org,
        'children': children,
        'departments': departments,
    })


@login_required
@permission_required('assets.change_organization', raise_exception=True)
def organization_update(request, pk):
    """编辑组织"""
    org = get_object_or_404(Organization, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        description = request.POST.get('description', '').strip()
        parent_id = request.POST.get('parent')
        if not name:
            messages.error(request, '组织名称不能为空')
        else:
            if code and Organization.objects.exclude(pk=org.pk).filter(code=code).exists():
                messages.error(request, '组织编码已被使用')
            else:
                parent = Organization.objects.filter(pk=parent_id).first() if parent_id else None
                org.name = name
                org.code = code or None
                org.description = description
                org.parent = parent
                org.save()
                messages.success(request, '组织信息已更新')
                return redirect('assets:organization_detail', pk=org.pk)
    return render(request, 'assets/organization_form.html', {
        'organization': org,
        'organizations': Organization.objects.exclude(pk=org.pk),
        'form_action': 'edit',
    })


@login_required
@permission_required('assets.delete_organization', raise_exception=True)
def organization_delete(request, pk):
    """删除组织"""
    org = get_object_or_404(Organization, pk=pk)
    if request.method == 'POST':
        name = org.name
        org.delete()
        messages.success(request, f'组织 {name} 已删除')
        return redirect('assets:organization_list')
    return render(request, 'assets/organization_confirm_delete.html', {'organization': org})


@login_required
@permission_required('assets.add_organization', raise_exception=True)
def bulk_import_organizations(request):
    """批量导入组织（简单占位，使用通用模板页面）"""
    if request.method == 'POST' and request.FILES.get('excel_file'):
        try:
            workbook = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = workbook.active
            success, errors = 0, 0
            for row in sheet.iter_rows(min_row=2, values_only=True):
                if not row or not row[0]:
                    continue
                name = str(row[0]).strip()
                code = str(row[1]).strip() if len(row) > 1 and row[1] else None
                if Organization.objects.filter(name=name).exists():
                    errors += 1
                    continue
                Organization.objects.create(name=name, code=code)
                success += 1
            if success:
                messages.success(request, f'成功导入 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录导入失败或重复')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
    return render(request, 'assets/bulk_import.html', {
        'import_type': 'organizations',
        'title': '批量导入组织',
    })


@login_required
@permission_required('assets.delete_organization', raise_exception=True)
def organization_batch_delete(request):
    """批量删除组织"""
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = Organization.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个组织')
        return redirect('assets:organization_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

# ==================== 资产文件/图片管理（最小实现） ====================
@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_upload_image(request, asset_id):
    asset = get_object_or_404(Asset, id=asset_id)
    # Kept for compatibility if frontend ever posts a single file named 'image'
    if request.method == 'POST':
        upload = request.FILES.get('image')
        if not upload:
            messages.error(request, '未选择图片')
            return redirect('assets:asset_detail', pk=asset.id)

        # 校验数量上限（最多10张）
        if asset.get_image_count() >= 10:
            messages.error(request, '已达到图片数量上限（最多10张）')
            return redirect('assets:asset_detail', pk=asset.id)

        # 校验类型与大小
        if not getattr(upload, 'content_type', '').startswith('image/'):
            messages.error(request, f'不支持的图片类型: {getattr(upload, "content_type", "")}')
            return redirect('assets:asset_detail', pk=asset.id)
        if upload.size > 2 * 1024 * 1024:
            messages.error(request, '图片大小不能超过2MB')
            return redirect('assets:asset_detail', pk=asset.id)

        # 创建记录
        img = AssetImage.objects.create(
            asset=asset,
            image=upload,
            title=request.POST.get('title', '').strip() or upload.name,
            description=request.POST.get('description', '').strip(),
            uploaded_by=request.user,
        )

        # 记录日志
        try:
            AssetLog.log_action(
                asset=asset,
                action='image_upload',
                operator=request.user,
                description=f'上传图片: {img.title}',
                new_value={'image_id': img.id, 'title': img.title},
                request=request,
                related_object=img,
            )
        except Exception:
            pass

        messages.success(request, '图片上传成功')
        return redirect('assets:asset_detail', pk=asset.id)
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_upload_images(request, asset_id):
    asset = get_object_or_404(Asset, id=asset_id)
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

    files = request.FILES.getlist('images')
    if not files:
        messages.error(request, '未选择图片')
        return redirect('assets:asset_detail', pk=asset.id)

    current = asset.get_image_count()
    if current + len(files) > 10:
        messages.error(request, f'最多只能上传10张图片，当前已有{current}张，最多还能上传{max(0, 10 - current)}张')
        return redirect('assets:asset_detail', pk=asset.id)

    batch_title = (request.POST.get('batch_title') or '').strip()
    batch_desc = (request.POST.get('batch_description') or '').strip()
    set_first_primary = bool(request.POST.get('set_first_as_primary'))

    created = []
    skipped = []
    for idx, f in enumerate(files, start=1):
        # 验证
        if not getattr(f, 'content_type', '').startswith('image/'):
            skipped.append(f'{f.name}(类型)')
            continue
        if f.size > 2 * 1024 * 1024:
            skipped.append(f'{f.name}(大小)')
            continue

        title = f'{batch_title}{idx}' if batch_title else f.name
        desc = batch_desc or ''
        img = AssetImage.objects.create(
            asset=asset,
            image=f,
            title=title,
            description=desc,
            uploaded_by=request.user,
        )
        created.append(img)

    # 设置第一张为主图（如请求）
    if set_first_primary and created:
        try:
            first = created[0]
            first.is_primary = True
            first.save(update_fields=['is_primary'])
        except Exception:
            pass

    # 日志
    if created:
        try:
            AssetLog.log_action(
                asset=asset,
                action='image_upload',
                operator=request.user,
                description=f'批量上传图片 {len(created)} 张',
                new_value={'count': len(created), 'ids': [i.id for i in created]},
                request=request,
            )
        except Exception:
            pass

    if skipped:
        messages.warning(request, f'部分图片跳过：{"、".join(skipped)}')
    messages.success(request, f'成功上传 {len(created)} 张图片')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_delete_image(request, asset_id):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)
    image_id = request.POST.get('image_id')
    asset = get_object_or_404(Asset, id=asset_id)
    if not image_id:
        messages.error(request, '缺少图片ID')
        return redirect('assets:asset_detail', pk=asset.id)
    # 处理主图删除（占位ID 'main'）
    if image_id == 'main':
        if asset.image:
            try:
                asset.image.delete(save=False)
            except Exception:
                pass
            asset.image = None
            asset.save(update_fields=['image'])
            try:
                AssetLog.log_action(
                    asset=asset,
                    action='image_delete',
                    operator=request.user,
                    description='删除主图片',
                    old_value={'type': 'main'},
                    request=request,
                )
            except Exception:
                pass
            messages.success(request, '主图片已删除')
        else:
            messages.info(request, '该资产无主图片')
        return redirect('assets:asset_detail', pk=asset.id)
    try:
        img = AssetImage.objects.get(id=image_id, asset=asset)
    except AssetImage.DoesNotExist:
        messages.error(request, '图片不存在')
        return redirect('assets:asset_detail', pk=asset.id)

    was_primary = img.is_primary
    title = img.title or '图片'
    img.delete()

    # 如果删除了主图，尝试设置另一张为主图
    if was_primary:
        next_img = asset.asset_images.order_by('sort_order', 'created_at').first()
        if next_img:
            next_img.is_primary = True
            next_img.save(update_fields=['is_primary'])

    try:
        AssetLog.log_action(
            asset=asset,
            action='image_delete',
            operator=request.user,
            description=f'删除图片: {title}',
            old_value={'image_id': int(image_id)},
            request=request,
        )
    except Exception:
        pass

    messages.success(request, '图片已删除')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_batch_delete_images(request, asset_id):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)
    asset = get_object_or_404(Asset, id=asset_id)
    ids = request.POST.getlist('image_ids') or request.POST.getlist('image_ids[]')
    if not ids:
        messages.error(request, '未选择要删除的图片')
        return redirect('assets:asset_detail', pk=asset.id)

    # 处理包含 'main' 的情况；并过滤非数字ID
    includes_main = 'main' in ids
    invalid_ids = [x for x in ids if (not str(x).isdigit()) and x != 'main']
    valid_ids = [int(x) for x in ids if str(x).isdigit()]

    imgs = list(AssetImage.objects.filter(asset=asset, id__in=valid_ids))
    if not imgs and not includes_main:
        messages.error(request, '所选图片不存在')
        return redirect('assets:asset_detail', pk=asset.id)

    had_primary = any(i.is_primary for i in imgs)
    count = len(imgs)

    # 删除主图
    if includes_main and asset.image:
        try:
            asset.image.delete(save=False)
        except Exception:
            pass
        asset.image = None
        asset.save(update_fields=['image'])
        count += 1

    for i in imgs:
        i.delete()

    # 如果包含主图，重设一张为主图
    if had_primary:
        next_img = asset.asset_images.order_by('sort_order', 'created_at').first()
        if next_img:
            next_img.is_primary = True
            next_img.save(update_fields=['is_primary'])

    try:
        AssetLog.log_action(
            asset=asset,
            action='image_delete',
            operator=request.user,
            description=f'批量删除图片 {count} 张',
            old_value={'ids': valid_ids, 'skipped': invalid_ids, 'includes_main': includes_main},
            request=request,
        )
    except Exception:
        pass

    if includes_main:
        messages.info(request, '已删除主图片')
    if invalid_ids:
        messages.warning(request, f'已忽略无效ID：{"、".join(invalid_ids)}')
    messages.success(request, f'已批量删除 {count} 张图片')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_set_primary_image(request, asset_id):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)
    asset = get_object_or_404(Asset, id=asset_id)
    image_id = request.POST.get('image_id')
    if not image_id:
        messages.error(request, '缺少图片ID')
        return redirect('assets:asset_detail', pk=asset.id)
    try:
        img = AssetImage.objects.get(id=image_id, asset=asset)
    except AssetImage.DoesNotExist:
        messages.error(request, '图片不存在')
        return redirect('assets:asset_detail', pk=asset.id)

    img.is_primary = True
    img.save(update_fields=['is_primary'])

    messages.success(request, '主图片设置成功')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_edit_image(request, asset_id):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)
    asset = get_object_or_404(Asset, id=asset_id)
    image_id = request.POST.get('image_id')
    title = (request.POST.get('title') or '').strip()
    desc = (request.POST.get('description') or '').strip()

    if not image_id:
        messages.error(request, '缺少图片ID')
        return redirect('assets:asset_detail', pk=asset.id)
    try:
        img = AssetImage.objects.get(id=image_id, asset=asset)
    except AssetImage.DoesNotExist:
        messages.error(request, '图片不存在')
        return redirect('assets:asset_detail', pk=asset.id)

    old = {'title': img.title, 'description': img.description}
    img.title = title
    img.description = desc
    img.save(update_fields=['title', 'description', 'updated_at'])

    try:
        AssetLog.log_action(
            asset=asset,
            action='update',
            operator=request.user,
            description=f'编辑图片信息: {title or img.id}',
            old_value=old,
            new_value={'title': img.title, 'description': img.description},
            request=request,
            related_object=img,
        )
    except Exception:
        pass

    messages.success(request, '图片信息已更新')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_upload_file(request, asset_id):
    asset = get_object_or_404(Asset, id=asset_id)
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

    upload = request.FILES.get('file')
    if not upload:
        messages.error(request, '未选择文件')
        return redirect('assets:asset_detail', pk=asset.id)

    # 校验大小（<=10MB）
    if upload.size > 10 * 1024 * 1024:
        messages.error(request, '文件大小不能超过10MB')
        return redirect('assets:asset_detail', pk=asset.id)

    # 校验扩展名
    allowed_ext = {'pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt'}
    ext = upload.name.split('.')[-1].lower() if '.' in upload.name else ''
    if ext not in allowed_ext:
        messages.error(request, f'不支持的文件类型: .{ext}')
        return redirect('assets:asset_detail', pk=asset.id)

    # 数量上限（最多20个文件）
    if not asset.can_add_more_files():
        messages.error(request, '已达到文件数量上限（最多20个）')
        return redirect('assets:asset_detail', pk=asset.id)

    title = (request.POST.get('title') or '').strip() or upload.name.rsplit('.', 1)[0]
    desc = (request.POST.get('description') or '').strip()
    af = AssetFile.objects.create(
        asset=asset,
        file=upload,
        title=title,
        description=desc,
        uploaded_by=request.user,
    )

    try:
        AssetLog.log_action(
            asset=asset,
            action='update',
            operator=request.user,
            description=f'上传文件: {af.title}',
            new_value={'file_id': af.id, 'title': af.title, 'type': af.file_type},
            request=request,
            related_object=af,
        )
    except Exception:
        pass

    messages.success(request, '文件上传成功')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_delete_file(request, asset_id):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)
    asset = get_object_or_404(Asset, id=asset_id)
    file_id = request.POST.get('file_id')
    if not file_id:
        messages.error(request, '缺少文件ID')
        return redirect('assets:asset_detail', pk=asset.id)
    try:
        af = AssetFile.objects.get(id=file_id, asset=asset)
    except AssetFile.DoesNotExist:
        messages.error(request, '文件不存在')
        return redirect('assets:asset_detail', pk=asset.id)

    title = af.title
    af.delete()

    try:
        AssetLog.log_action(
            asset=asset,
            action='update',
            operator=request.user,
            description=f'删除文件: {title}',
            old_value={'file_id': int(file_id)},
            request=request,
        )
    except Exception:
        pass

    messages.success(request, '文件已删除')
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_edit_file(request, asset_id):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)
    asset = get_object_or_404(Asset, id=asset_id)
    file_id = request.POST.get('file_id')
    title = (request.POST.get('title') or '').strip()
    desc = (request.POST.get('description') or '').strip()

    if not file_id:
        messages.error(request, '缺少文件ID')
        return redirect('assets:asset_detail', pk=asset.id)
    try:
        af = AssetFile.objects.get(id=file_id, asset=asset)
    except AssetFile.DoesNotExist:
        messages.error(request, '文件不存在')
        return redirect('assets:asset_detail', pk=asset.id)

    old = {'title': af.title, 'description': af.description}
    af.title = title
    af.description = desc
    af.save(update_fields=['title', 'description', 'updated_at'])

    try:
        AssetLog.log_action(
            asset=asset,
            action='update',
            operator=request.user,
            description=f'编辑文件信息: {af.title}',
            old_value=old,
            new_value={'title': af.title, 'description': af.description},
            request=request,
            related_object=af,
        )
    except Exception:
        pass

    messages.success(request, '文件信息已更新')
    return redirect('assets:asset_detail', pk=asset.id)

# ==================== 资产文件安全在线查看 ====================
@login_required
@permission_required('assets.view_asset', raise_exception=True)
def asset_file_view(request, asset_id, file_id):
    """安全在线查看资产文件。
    - 仅允许已登录且具有查看资产权限的用户访问
    - 校验文件是否属于指定资产
    - PDF 使用 inline 在浏览器中打开，其它类型默认为下载
    """
    from django.http import FileResponse, Http404
    import mimetypes
    import os

    asset = get_object_or_404(Asset, id=asset_id)
    try:
        af = AssetFile.objects.get(id=file_id, asset=asset)
    except AssetFile.DoesNotExist:
        raise Http404('文件不存在')

    if not af.file:
        raise Http404('文件不存在')

    file_path = af.file.path
    if not os.path.exists(file_path):
        raise Http404('文件不存在')

    content_type, _ = mimetypes.guess_type(file_path)

    # 为了避免中文标题乱码，使用原始存储名或标题作为下载名
    filename = af.title or os.path.basename(file_path)

    # PDF 内联，其他默认下载（部分浏览器也可内联预览文本）
    if content_type == 'application/pdf':
        response = FileResponse(open(file_path, 'rb'), content_type=content_type, filename=filename)
        response['Content-Disposition'] = f'inline; filename="{filename}"'
    else:
        response = FileResponse(open(file_path, 'rb'), content_type=content_type or 'application/octet-stream', filename=filename)
        response['Content-Disposition'] = f'attachment; filename="{filename}"'

    return response

# ==================== 资产类别管理视图（最小实现） ====================
@login_required
@permission_required('assets.view_assetcategory', raise_exception=True)
def category_list(request):
    q = request.GET.get('q', '').strip()
    qs = AssetCategory.objects.all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(code__icontains=q))
    paginator = Paginator(qs.order_by('name'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/category_list.html', {'page_obj': page_obj, 'query': q})


@login_required
@permission_required('assets.add_assetcategory', raise_exception=True)
def category_create(request):
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        if not name or not code:
            messages.error(request, '名称与编码为必填')
        else:
            AssetCategory.objects.create(name=name, code=code)
            messages.success(request, '类别创建成功')
            return redirect('assets:category_list')
    return render(request, 'assets/category_form.html', {'form_action': 'create'})


@login_required
@permission_required('assets.change_assetcategory', raise_exception=True)
def category_update(request, pk):
    category = get_object_or_404(AssetCategory, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        if not name or not code:
            messages.error(request, '名称与编码为必填')
        else:
            category.name = name
            category.code = code
            category.save()
            messages.success(request, '类别已更新')
            return redirect('assets:category_list')
    return render(request, 'assets/category_form.html', {'category': category, 'form_action': 'edit'})


@login_required
@permission_required('assets.delete_assetcategory', raise_exception=True)
def category_delete(request, pk):
    category = get_object_or_404(AssetCategory, pk=pk)
    if request.method == 'POST':
        name = category.name
        category.delete()
        messages.success(request, f'类别 {name} 已删除')
        return redirect('assets:category_list')
    return render(request, 'assets/category_confirm_delete.html', {'category': category})


@login_required
@permission_required('assets.add_assetcategory', raise_exception=True)
def bulk_import_categories(request):
    if request.method == 'POST' and request.FILES.get('excel_file'):
        try:
            wb = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = wb.active
            success, errors = 0, 0
            for row in sheet.iter_rows(min_row=2, values_only=True):
                if not row or not row[0] or not row[1]:
                    errors += 1
                    continue
                name = str(row[0]).strip()
                code = str(row[1]).strip()
                if AssetCategory.objects.filter(code=code).exists():
                    errors += 1
                    continue
                AssetCategory.objects.create(name=name, code=code)
                success += 1
            if success:
                messages.success(request, f'成功导入 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录导入失败或重复')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
    return render(request, 'assets/bulk_import.html', {'import_type': 'categories', 'title': '批量导入类别'})


@login_required
@permission_required('assets.delete_assetcategory', raise_exception=True)
def category_batch_delete(request):
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = AssetCategory.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个类别')
        return redirect('assets:category_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

# ==================== 耗材管理视图（优化版） ====================
@login_required
@permission_required('assets.view_consumable', raise_exception=True)
def consumable_list(request):
    # 统一筛选参数：兼容 search/status/supplier，同时保留原有参数
    q = (request.GET.get('q') or request.GET.get('search') or '').strip()
    warehouse_id = request.GET.get('warehouse')
    category_id = request.GET.get('category')
    supplier_id = request.GET.get('supplier')
    low_stock = (request.GET.get('low_stock') or '').strip()
    status = (request.GET.get('status') or '').strip()
    sort = (request.GET.get('sort') or 'name').strip()
    order = (request.GET.get('order') or 'asc').strip()
    per_page = request.GET.get('per_page')

    try:
        per_page = int(per_page) if per_page else 20
    except Exception:
        per_page = 20
    if per_page not in [10, 20, 50, 100]:
        per_page = 20

    qs = Consumable.objects.select_related('warehouse', 'category', 'supplier').all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(code__icontains=q))
    if warehouse_id:
        qs = qs.filter(warehouse_id=warehouse_id)
    if category_id:
        qs = qs.filter(category_id=category_id)
    if supplier_id:
        qs = qs.filter(supplier_id=supplier_id)
    if low_stock in ['1', 'true', 'True', 'yes']:
        qs = qs.filter(quantity__lte=F('min_quantity'))
    elif status:
        if status == 'out_of_stock':
            qs = qs.filter(quantity__lte=0)
        elif status == 'low_stock':
            qs = qs.filter(quantity__gt=0, quantity__lte=F('min_quantity'))
        elif status == 'normal':
            qs = qs.filter(quantity__gt=F('min_quantity'))

    sort_map = {
        'name': 'name',
        'code': 'code',
        'quantity': 'quantity',
        'updated_at': 'updated_at',
    }
    sort_field = sort_map.get(sort, 'name')
    if order == 'desc':
        sort_field = f'-{sort_field}'

    paginator = Paginator(qs.order_by(sort_field), per_page)
    page_obj = paginator.get_page(request.GET.get('page'))
    # 下拉选项所需数据
    warehouses = Warehouse.objects.all().order_by('name')
    categories = AssetCategory.objects.all().order_by('name')
    suppliers = Supplier.objects.all().order_by('name')

    return render(request, 'assets/consumable_list.html', {
        'page_obj': page_obj,
        'search_query': q,
        'filters': {
            'warehouse': warehouse_id,
            'category': category_id,
            'low_stock': low_stock,
            'supplier': supplier_id,
            'status': status,
        },
        'sort': sort,
        'order': order,
        'per_page': per_page,
        'warehouses': warehouses,
        'categories': categories,
        'suppliers': suppliers,
    })


@login_required
@require_POST
def consumable_quick_stock_out(request):
    """快速出库耗材"""
    try:
        consumable_id = request.POST.get('consumable_id')
        quantity = Decimal(request.POST.get('quantity', '0'))
        description = request.POST.get('description', '').strip()
        
        if not consumable_id or not quantity or quantity <= 0:
            return JsonResponse({'status': 'error', 'message': '无效的参数'}, status=400)
        
        with transaction.atomic():
            consumable = get_object_or_404(Consumable.objects.select_for_update(), pk=consumable_id)
            
            # 检查库存是否足够
            if consumable.quantity < quantity:
                return JsonResponse({
                    'status': 'error', 
                    'message': f'库存不足，当前库存: {consumable.quantity}'
                }, status=400)
            
            # 更新库存数量
            consumable.quantity -= quantity
            consumable.save()
            
            # 记录库存日志
            log = InventoryLog.objects.create(
                transaction_type='out',
                consumable=consumable,
                quantity=quantity,
                remaining_quantity=consumable.quantity,
                operator=request.user,
                description=description or '快速出库'
            )
            
            return JsonResponse({
                'status': 'success',
                'message': '出库成功',
                'new_quantity': str(consumable.quantity),
                'log_id': log.id
            })
            
    except Exception as e:
        logger.error(f'快速出库失败: {str(e)}', exc_info=True)
        return JsonResponse({'status': 'error', 'message': f'出库失败: {str(e)}'}, status=500)


@login_required
@require_POST
def consumable_quick_stock_in(request):
    """快速入库耗材"""
    try:
        consumable_id = request.POST.get('consumable_id')
        quantity = Decimal(request.POST.get('quantity', '0'))
        notes = request.POST.get('notes', '')
        
        if not consumable_id or not quantity or quantity <= 0:
            return JsonResponse({'status': 'error', 'message': '无效的参数'}, status=400)
        
        consumable = get_object_or_404(Consumable, pk=consumable_id)
        
        # 更新库存数量
        consumable.quantity += quantity
        consumable.save()
        
        # 记录库存日志
        InventoryLog.objects.create(
            transaction_type='in',
            consumable=consumable,
            quantity=quantity,
            remaining_quantity=consumable.quantity,
            operator=request.user,
            description=f'快速入库: {notes}'
        )
        
        return JsonResponse({
            'status': 'success',
            'message': '入库成功',
            'new_quantity': str(consumable.quantity)
        })
        
    except Exception as e:
        logger.error(f'快速入库失败: {str(e)}', exc_info=True)
        return JsonResponse({'status': 'error', 'message': f'入库失败: {str(e)}'}, status=500)


@login_required
@permission_required('assets.add_consumable', raise_exception=True)
def consumable_create(request):
    # 选项数据
    warehouses = Warehouse.objects.all().order_by('name')
    categories = AssetCategory.objects.all().order_by('name')
    suppliers = Supplier.objects.all().order_by('name')

    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        quantity = request.POST.get('quantity', '0').strip()
        unit = request.POST.get('unit', '').strip()
        category_id = request.POST.get('category')
        warehouse_id = request.POST.get('warehouse')
        supplier_id = request.POST.get('supplier') or None
        min_quantity = request.POST.get('min_quantity') or None
        purchase_price = request.POST.get('purchase_price') or None
        description = request.POST.get('description', '').strip()

        try:
            qty = float(quantity)
        except Exception:
            qty = 0

        errors = []
        if not name:
            errors.append('名称为必填')
        if not code:
            errors.append('编码为必填')
        if not unit:
            errors.append('单位为必填')
        if not category_id:
            errors.append('类别为必选')
        if not warehouse_id:
            errors.append('仓库为必选')

        if errors:
            if is_ajax:
                html = render(request, 'assets/partials/consumable_form_fields.html', {
                    'name': name,
                    'code': code,
                    'quantity': quantity,
                    'unit': unit,
                    'category_id': category_id,
                    'warehouse_id': warehouse_id,
                    'supplier_id': supplier_id,
                    'min_quantity': min_quantity,
                    'purchase_price': purchase_price,
                    'description': description,
                    'warehouses': warehouses,
                    'categories': categories,
                    'suppliers': suppliers,
                    'errors': errors,
                }).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html}, status=400)
            messages.error(request, '，'.join(errors))
        else:
            Consumable.objects.create(
                name=name,
                code=code,
                quantity=qty,
                unit=unit,
                category_id=category_id,
                warehouse_id=warehouse_id,
                supplier_id=supplier_id,
                min_quantity=min_quantity or 0,
                purchase_price=purchase_price or None,
                description=description,
            )
            if is_ajax:
                return JsonResponse({'success': True})
            messages.success(request, '耗材创建成功')
            return redirect('assets:consumable_list')

    if is_ajax:
        html = render(request, 'assets/partials/consumable_form_fields.html', {
            'warehouses': warehouses,
            'categories': categories,
            'suppliers': suppliers,
        }).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    return render(request, 'assets/consumable_form.html', {
        'form_action': 'create',
        'warehouses': warehouses,
        'categories': categories,
        'suppliers': suppliers,
    })


@login_required
@permission_required('assets.view_consumable', raise_exception=True)
def consumable_detail(request, pk):
    consumable = get_object_or_404(Consumable, pk=pk)
    return render(request, 'assets/consumable_detail.html', {'consumable': consumable})


@login_required
@permission_required('assets.change_consumable', raise_exception=True)
def consumable_update(request, pk):
    consumable = get_object_or_404(Consumable, pk=pk)
    warehouses = Warehouse.objects.all().order_by('name')
    categories = AssetCategory.objects.all().order_by('name')
    suppliers = Supplier.objects.all().order_by('name')

    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        quantity = request.POST.get('quantity', '').strip()
        unit = request.POST.get('unit', '').strip()
        category_id = request.POST.get('category')
        warehouse_id = request.POST.get('warehouse')
        supplier_id = request.POST.get('supplier') or None
        min_quantity = request.POST.get('min_quantity') or None
        purchase_price = request.POST.get('purchase_price') or None
        description = request.POST.get('description', '').strip()

        try:
            qty = float(quantity)
        except Exception:
            qty = consumable.quantity

        errors = []
        if not name:
            errors.append('名称为必填')
        if not code:
            errors.append('编码为必填')
        if not unit:
            errors.append('单位为必填')
        if not category_id:
            errors.append('类别为必选')
        if not warehouse_id:
            errors.append('仓库为必选')

        if errors:
            if is_ajax:
                html = render(request, 'assets/partials/consumable_form_fields.html', {
                    'consumable': consumable,
                    'name': name,
                    'code': code,
                    'quantity': quantity,
                    'unit': unit,
                    'category_id': category_id,
                    'warehouse_id': warehouse_id,
                    'supplier_id': supplier_id,
                    'min_quantity': min_quantity,
                    'purchase_price': purchase_price,
                    'description': description,
                    'warehouses': warehouses,
                    'categories': categories,
                    'suppliers': suppliers,
                    'errors': errors,
                }).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html}, status=400)
            messages.error(request, '，'.join(errors))
        else:
            consumable.name = name
            consumable.code = code
            consumable.quantity = qty
            consumable.unit = unit
            consumable.category_id = category_id
            consumable.warehouse_id = warehouse_id
            consumable.supplier_id = supplier_id
            consumable.min_quantity = min_quantity or 0
            consumable.purchase_price = purchase_price or None
            consumable.description = description
            consumable.save()
            if is_ajax:
                return JsonResponse({'success': True})
            messages.success(request, '耗材已更新')
            return redirect('assets:consumable_detail', pk=consumable.pk)

    if is_ajax:
        html = render(request, 'assets/partials/consumable_form_fields.html', {
            'consumable': consumable,
            'warehouses': warehouses,
            'categories': categories,
            'suppliers': suppliers,
        }).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    return render(request, 'assets/consumable_form.html', {
        'consumable': consumable,
        'form_action': 'edit',
        'warehouses': warehouses,
        'categories': categories,
        'suppliers': suppliers,
    })


@login_required
@permission_required('assets.delete_consumable', raise_exception=True)
def consumable_delete(request, pk):
    consumable = get_object_or_404(Consumable, pk=pk)
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        name = consumable.name
        consumable.delete()
        if is_ajax:
            return JsonResponse({'success': True})
        messages.success(request, f'耗材 {name} 已删除')
        return redirect('assets:consumable_list')
    # GET
    if is_ajax:
        html = render(request, 'assets/partials/consumable_confirm_delete.html', {
            'consumable': consumable
        }).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    return render(request, 'assets/consumable_confirm_delete.html', {'consumable': consumable})


@login_required
@permission_required('assets.view_consumable', raise_exception=True)
def consumable_export(request):
    """导出耗材数据（尊重当前筛选）。默认导出CSV。
    支持参数：
      - q 或 search: 名称/编码模糊查询
      - warehouse, category, supplier: 过滤
      - status: low_stock/normal/out_of_stock
      - low_stock: 1/true 时按最低库存过滤
      - format: csv
    """
    from django.db.models import F, Q
    from django.http import HttpResponse
    import csv
    from datetime import datetime

    fmt = (request.GET.get('format') or 'csv').lower()

    # 解析筛选参数（兼容 consumable_list 与模板使用的命名）
    q = (request.GET.get('q') or request.GET.get('search') or '').strip()
    warehouse_id = request.GET.get('warehouse')
    category_id = request.GET.get('category')
    supplier_id = request.GET.get('supplier')
    low_stock_flag = (request.GET.get('low_stock') or '').strip()
    status = (request.GET.get('status') or '').strip()

    qs = Consumable.objects.select_related('warehouse', 'category', 'supplier').all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(code__icontains=q))
    if warehouse_id:
        qs = qs.filter(warehouse_id=warehouse_id)
    if category_id:
        qs = qs.filter(category_id=category_id)
    if supplier_id:
        qs = qs.filter(supplier_id=supplier_id)
    if low_stock_flag in ['1', 'true', 'True', 'yes']:
        qs = qs.filter(quantity__lte=F('min_quantity'))
    elif status:
        if status == 'out_of_stock':
            qs = qs.filter(quantity__lte=0)
        elif status == 'low_stock':
            qs = qs.filter(quantity__gt=0, quantity__lte=F('min_quantity'))
        elif status == 'normal':
            qs = qs.filter(quantity__gt=F('min_quantity'))

    # 仅实现 CSV
    if fmt != 'csv':
        fmt = 'csv'

    response = HttpResponse(content_type='text/csv; charset=utf-8')
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    response['Content-Disposition'] = f'attachment; filename="consumables_{timestamp}.csv"'

    writer = csv.writer(response)
    writer.writerow(['名称', '编码', '类别', '仓库', '单位', '库存数量', '最低库存', '采购单价', '供应商'])
    for c in qs.order_by('name'):
        writer.writerow([
            c.name or '',
            c.code or '',
            (c.category.name if getattr(c, 'category', None) else ''),
            (c.warehouse.name if getattr(c, 'warehouse', None) else ''),
            c.unit or '',
            c.quantity if c.quantity is not None else 0,
            c.min_quantity if c.min_quantity is not None else 0,
            f"{c.purchase_price:.2f}" if getattr(c, 'purchase_price', None) is not None else '',
            (c.supplier.name if getattr(c, 'supplier', None) else ''),
        ])

    return response


@login_required
@permission_required('assets.view_consumable', raise_exception=True)
def consumable_low_stock(request):
    """将请求重定向到耗材列表，并强制启用低库存筛选，同时保留其它查询参数。"""
    from django.urls import reverse
    params = request.GET.copy()
    params['low_stock'] = '1'
    target = reverse('assets:consumable_list')
    if params:
        target = f"{target}?{params.urlencode()}"
    return redirect(target)

@login_required
@permission_required('assets.add_consumable', raise_exception=True)
def bulk_import_consumables(request):
    if request.method == 'POST' and request.FILES.get('excel_file'):
        try:
            wb = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = wb.active
            success, errors = 0, 0
            for row in sheet.iter_rows(min_row=2, values_only=True):
                if not row or not row[0] or not row[1]:
                    errors += 1
                    continue
                name = str(row[0]).strip()
                code = str(row[1]).strip()
                qty = int(row[2]) if len(row) > 2 and row[2] is not None else 0
                if Consumable.objects.filter(code=code).exists():
                    errors += 1
                    continue
                Consumable.objects.create(name=name, code=code, quantity=qty)
                success += 1
            if success:
                messages.success(request, f'成功导入 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录导入失败或重复')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
    return render(request, 'assets/bulk_import.html', {'import_type': 'consumables', 'title': '批量导入耗材'})


@login_required
@permission_required('assets.delete_consumable', raise_exception=True)
def consumable_batch_delete(request):
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = Consumable.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个耗材')
        return redirect('assets:consumable_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


@login_required
@permission_required('assets.change_consumable', raise_exception=True)
def consumable_batch_update_stock(request):
    if request.method == 'POST':
        updates = request.POST.getlist('updates[]') or []
        updated = 0
        for item in updates:
            try:
                # 期望格式: id:delta
                cid, delta = item.split(':', 1)
                c = Consumable.objects.get(pk=int(cid))
                c.quantity = (c.quantity or 0) + int(delta)
                c.save()
                updated += 1
            except Exception:
                continue
        messages.success(request, f'已更新 {updated} 条库存')
        return redirect('assets:consumable_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


# ==================== 库存记录视图（优化版） ====================
@login_required
@permission_required('assets.view_inventorylog', raise_exception=True)
def inventory_log_list(request):
    q = (request.GET.get('q') or '').strip()
    trx_type = (request.GET.get('type') or '').strip()  # 'in' | 'out'
    operator_id = request.GET.get('operator')
    date_from = (request.GET.get('date_from') or '').strip()  # YYYY-MM-DD
    date_to = (request.GET.get('date_to') or '').strip()      # YYYY-MM-DD
    per_page = request.GET.get('per_page')

    try:
        per_page = int(per_page) if per_page else 20
    except Exception:
        per_page = 20
    if per_page not in [10, 20, 50, 100]:
        per_page = 20

    qs = InventoryLog.objects.select_related('consumable', 'operator').all()
    if q:
        qs = qs.filter(Q(consumable__name__icontains=q) | Q(consumable__code__icontains=q))
    if trx_type in ['in', 'out']:
        qs = qs.filter(transaction_type=trx_type)
    if operator_id:
        qs = qs.filter(operator_id=operator_id)
    # 日期范围
    try:
        if date_from:
            start = datetime.strptime(date_from, '%Y-%m-%d')
            qs = qs.filter(created_at__gte=start)
    except Exception:
        pass
    try:
        if date_to:
            end = datetime.strptime(date_to, '%Y-%m-%d') + timedelta(days=1)
            qs = qs.filter(created_at__lt=end)
    except Exception:
        pass

    paginator = Paginator(qs.order_by('-created_at'), per_page)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/inventory_log_list.html', {
        'page_obj': page_obj,
        'query': q,
        'filters': {
            'type': trx_type,
            'operator': operator_id,
            'date_from': date_from,
            'date_to': date_to,
        },
        'per_page': per_page,
    })


@login_required
@permission_required('assets.view_inventorylog', raise_exception=True)
def inventory_log_detail(request, pk):
    log = get_object_or_404(InventoryLog, pk=pk)
    return render(request, 'assets/inventory_log_detail.html', {'log': log})


@login_required
@permission_required('assets.view_consumable', raise_exception=True)
def consumable_records(request, pk):
    consumable = get_object_or_404(Consumable, pk=pk)
    logs = InventoryLog.objects.filter(consumable=consumable).order_by('-created_at')
    paginator = Paginator(logs, 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/consumable_records.html', {'consumable': consumable, 'page_obj': page_obj})

# ==================== 资产管理视图（最小实现） ====================
@login_required
@permission_required('assets.view_asset', raise_exception=True)
def asset_list(request):
    # 搜索与筛选参数
    search_query = (request.GET.get('search') or '').strip()
    category_id = request.GET.get('category')
    organization_id = request.GET.get('organization')
    status_val = (request.GET.get('status') or '').strip()
    department_id = request.GET.get('department')
    user_id = request.GET.get('user')
    sort = (request.GET.get('sort') or '').strip()
    order = (request.GET.get('order') or 'asc').strip()
    per_page = request.GET.get('per_page')

    try:
        per_page = int(per_page) if per_page else 20
    except Exception:
        per_page = 20
    if per_page not in [10, 20, 50, 100]:
        per_page = 20

    # 基础查询
    qs = Asset.objects.select_related(
        'category', 'department', 'user', 'ledger', 'ledger__organization'
    ).all()

    # 搜索
    if search_query:
        qs = qs.filter(Q(name__icontains=search_query) | Q(asset_number__icontains=search_query))

    # 筛选
    if category_id:
        qs = qs.filter(category_id=category_id)
    if organization_id:
        qs = qs.filter(ledger__organization_id=organization_id)
    if status_val:
        qs = qs.filter(status=status_val)
    if department_id:
        qs = qs.filter(department_id=department_id)
    if user_id:
        if user_id == 'unassigned':
            qs = qs.filter(user__isnull=True)
        else:
            qs = qs.filter(user_id=user_id)

    # 排序映射
    sort_map = {
        'organization': 'ledger__organization__name',
        'asset_number': 'asset_number',
        'name': 'name',
        'category': 'category__name',
        'status': 'status',
        'department': 'department__name',
        'user': 'user__username',
        'purchase_price': 'purchase_price',
        'purchase_date': 'purchase_date',
    }
    order_by = '-id'  # 默认
    if sort in sort_map:
        field = sort_map[sort]
        order_by = field if order == 'asc' else f'-{field}'
    qs = qs.order_by(order_by, '-id')  # 次级保证稳定

    # 下拉数据
    categories = AssetCategory.objects.all().order_by('name')
    organizations = Organization.objects.all().order_by('name')
    departments = Department.objects.all().order_by('name')
    users = User.objects.all().order_by('username')

    paginator = Paginator(qs, per_page)
    page_obj = paginator.get_page(request.GET.get('page'))

    ctx = {
        'page_obj': page_obj,
        'search_query': search_query,
        'categories': categories,
        'organizations': organizations,
        'departments': departments,
        'users': users,
        'per_page': per_page,
        'sort': sort,
        'order': order,
    }
    # 支持通过 AJAX 获取片段以优化刷新性能
    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        fragment = request.GET.get('fragment')
        if fragment == 'table':
            html = render(request, 'assets/partials/asset_table_body.html', ctx).content.decode('utf-8')
            return HttpResponse(html)
        elif fragment == 'header':
            html = render(request, 'assets/partials/asset_list_header.html', ctx).content.decode('utf-8')
            return HttpResponse(html)
        elif fragment == 'pagination':
            html = render(request, 'assets/partials/asset_pagination.html', ctx).content.decode('utf-8')
            return HttpResponse(html)
    return render(request, 'assets/asset_list.html', ctx)


def _build_asset_form_context(asset=None):
    """构建资产表单所需的下拉数据与已选ID"""
    categories = AssetCategory.objects.all().order_by('name')
    organizations = Organization.objects.all().order_by('name')
    ledgers = AssetLedger.objects.select_related('organization').all().order_by('organization__name', 'name')
    departments = Department.objects.all().order_by('name')
    locations = Location.objects.all().order_by('name')
    users = User.objects.all().order_by('username')
    suppliers = Supplier.objects.all().order_by('name')
    contracts = Contract.objects.all().order_by('name')

    context = {
        'categories': categories,
        'organizations': organizations,
        'ledgers': ledgers,
        'departments': departments,
        'locations': locations,
        'users': users,
        'suppliers': suppliers,
        'contracts': contracts,
    }

    # 选中项（模板中可用 *_id 变量或 asset.* 本身）
    if asset is not None:
        context.update({
            'organization_id': getattr(getattr(asset.ledger, 'organization', None), 'id', None),
            'ledger_id': getattr(asset, 'ledger_id', None),
            'department_id': getattr(asset, 'department_id', None),
            'location_id': getattr(asset, 'location_id', None),
            'user_id': getattr(asset, 'user_id', None),
            'supplier_id': getattr(asset, 'supplier_id', None),
            'contract_id': getattr(asset, 'contract_id', None),
            'category_id': getattr(asset, 'category_id', None),
        })

    return context

@login_required
@permission_required('assets.add_asset', raise_exception=True)
def asset_create(request):
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        # 基本字段
        name = request.POST.get('name', '').strip()
        asset_number = request.POST.get('asset_number', '').strip()
        category_id = request.POST.get('category')
        status = (request.POST.get('status') or 'idle').strip()
        # 归属与关联
        organization_id = request.POST.get('organization')  # 仅用于前端筛选，不直接保存
        ledger_id = request.POST.get('ledger')
        department_id = request.POST.get('department')
        location_id = request.POST.get('location')
        user_id = request.POST.get('user')
        supplier_id = request.POST.get('supplier')
        contract_id = request.POST.get('contract')
        # 财务信息
        purchase_date_str = request.POST.get('purchase_date', '').strip()
        purchase_price_str = request.POST.get('purchase_price', '').strip()
        warranty_period_str = request.POST.get('warranty_period', '').strip()
        depreciation_rate_str = request.POST.get('depreciation_rate', '').strip()
        description = request.POST.get('description', '').strip()
        image_file = request.FILES.get('image')

        errors = []
        if not name:
            errors.append('资产名称为必填')
        if not asset_number:
            errors.append('资产编号为必填')
        if not category_id:
            errors.append('资产类别为必填')
        if not purchase_date_str:
            errors.append('启用日期为必填')
        if not purchase_price_str:
            errors.append('采购价格为必填')

        # 转换与校验
        purchase_date = None
        purchase_price = None
        warranty_period = 0
        depreciation_rate = Decimal('0')
        if not errors:
            try:
                purchase_date = datetime.strptime(purchase_date_str, '%Y-%m-%d').date()
            except Exception:
                errors.append('启用日期格式不正确')
            try:
                purchase_price = Decimal(purchase_price_str)
            except Exception:
                errors.append('采购价格格式不正确')
            if warranty_period_str:
                try:
                    warranty_period = int(warranty_period_str)
                except Exception:
                    errors.append('保修期格式不正确')
            if depreciation_rate_str:
                try:
                    depreciation_rate = Decimal(depreciation_rate_str)
                except Exception:
                    errors.append('折旧率格式不正确')

        if errors:
            # 回填上下文
            ctx = _build_asset_form_context()
            ctx.update({
                'form_action': 'create',
                'name': name,
                'asset_number': asset_number,
                'category_id': int(category_id) if category_id else None,
                'status': status,
                'organization_id': int(organization_id) if organization_id else None,
                'ledger_id': int(ledger_id) if ledger_id else None,
                'department_id': int(department_id) if department_id else None,
                'location_id': int(location_id) if location_id else None,
                'user_id': int(user_id) if user_id else None,
                'supplier_id': int(supplier_id) if supplier_id else None,
                'contract_id': int(contract_id) if contract_id else None,
                'purchase_date': purchase_date_str,
                'purchase_price': purchase_price_str,
                'warranty_period': warranty_period_str,
                'depreciation_rate': depreciation_rate_str,
                'description': description,
            })
            if is_ajax:
                # 返回部分模板HTML，供前端内联展示错误
                html = render(request, 'assets/_asset_form_body.html', ctx).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html}, status=400)
            else:
                for e in errors:
                    messages.error(request, e)
                template_name = 'assets/asset_form_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_form.html'
                return render(request, template_name, ctx)

        # 创建资产
        asset = Asset.objects.create(
            name=name,
            asset_number=asset_number,
            category_id=category_id or None,
            status=status or 'idle',
            ledger_id=ledger_id or None,
            department_id=department_id or None,
            location_id=location_id or None,
            user_id=user_id or None,
            supplier_id=supplier_id or None,
            contract_id=contract_id or None,
            purchase_date=purchase_date,
            purchase_price=purchase_price,
            warranty_period=warranty_period or 0,
            depreciation_rate=depreciation_rate or Decimal('0'),
            description=description,
        )
        if image_file:
            asset.image = image_file
            asset.save(update_fields=['image'])

        if is_ajax:
            return JsonResponse({'success': True, 'message': '资产创建成功', 'asset_id': asset.id})
        messages.success(request, '资产创建成功')
        if request.GET.get('modal') == '1':
            # 成功后保持在创建模态页，清空表单以便继续创建
            ctx = _build_asset_form_context()
            ctx.update({'form_action': 'create'})
            return render(request, 'assets/asset_form_modal.html', ctx)
        else:
            return redirect('assets:asset_list')

    # GET 渲染
    ctx = _build_asset_form_context()
    ctx.update({'form_action': 'create'})
    if is_ajax:
        html = render(request, 'assets/_asset_form_body.html', ctx).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    template_name = 'assets/asset_form_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_form.html'
    return render(request, template_name, ctx)


@login_required
@permission_required('assets.view_asset', raise_exception=True)
def asset_detail(request, pk):
    asset = get_object_or_404(Asset, pk=pk)

    # 日志筛选与分页参数
    action = (request.GET.get('action') or '').strip()
    operator_id = request.GET.get('operator')
    date_from = (request.GET.get('date_from') or '').strip()
    date_to = (request.GET.get('date_to') or '').strip()
    keyword = (request.GET.get('keyword') or '').strip()
    per_page = request.GET.get('per_page')
    try:
        per_page = int(per_page) if per_page else 20
    except Exception:
        per_page = 20
    if per_page not in [10, 20, 50, 100]:
        per_page = 20

    # 构建查询
    logs_qs = AssetLog.objects.select_related('operator').filter(asset=asset)
    if action and action != 'all':
        logs_qs = logs_qs.filter(action=action)
    if operator_id:
        try:
            logs_qs = logs_qs.filter(operator_id=int(operator_id))
        except Exception:
            pass
    # 日期范围：闭区间（包含起止日）
    if date_from:
        try:
            from datetime import datetime
            dt_from = datetime.strptime(date_from, '%Y-%m-%d')
            logs_qs = logs_qs.filter(created_at__date__gte=dt_from.date())
        except Exception:
            pass
    if date_to:
        try:
            from datetime import datetime
            dt_to = datetime.strptime(date_to, '%Y-%m-%d')
            logs_qs = logs_qs.filter(created_at__date__lte=dt_to.date())
        except Exception:
            pass
    if keyword:
        logs_qs = logs_qs.filter(Q(description__icontains=keyword))
    logs_qs = logs_qs.order_by('-created_at')

    # 导出CSV（不分页）
    if (request.GET.get('export') or '').lower() == 'csv':
        from django.http import HttpResponse
        import csv
        from datetime import datetime as _dt
        filename = f"asset_{asset.id}_logs_{_dt.now().strftime('%Y%m%d_%H%M%S')}.csv"
        response = HttpResponse(content_type='text/csv; charset=utf-8')
        response['Content-Disposition'] = f'attachment; filename="{filename}"'
        # 写入CSV
        writer = csv.writer(response)
        writer.writerow(['操作类型', '操作人', '描述', '操作时间', 'IP 地址'])
        for log in logs_qs.select_related('operator').iterator():
            operator_name = ''
            if getattr(log, 'operator', None):
                operator_name = getattr(log.operator, 'get_full_name', lambda: '')() or log.operator.username or ''
            writer.writerow([
                log.get_action_display(),
                operator_name,
                (log.description or '').replace('\r', ' ').replace('\n', ' ').strip(),
                log.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                getattr(log, 'ip_address', '') or '',
            ])
        return response

    logs_count = logs_qs.count()
    paginator = Paginator(logs_qs, per_page)
    logs_page = paginator.get_page(request.GET.get('page'))

    # 可选操作人列表（仅限该资产有日志的操作者）
    operator_ids = AssetLog.objects.filter(asset=asset).values_list('operator_id', flat=True).distinct()
    operators = User.objects.filter(id__in=list(operator_ids)).order_by('username')

    # 组装基础查询串（用于分页链接复用筛选参数）
    def _qs_pair(name, val):
        return f"{name}={val}" if val not in [None, ''] else ''
    base_params = [
        _qs_pair('action', action),
        _qs_pair('operator', operator_id),
        _qs_pair('date_from', date_from),
        _qs_pair('date_to', date_to),
        _qs_pair('keyword', keyword),
        _qs_pair('per_page', per_page),
    ]
    qs_base = '&'.join([p for p in base_params if p])

    ctx = {
        'asset': asset,
        'logs_page': logs_page,
        'logs_count': logs_count,
        'filters': {
            'action': action,
            'operator': operator_id,
            'date_from': date_from,
            'date_to': date_to,
            'keyword': keyword,
        },
        'per_page': per_page,
        'operators': operators,
        'qs_base': qs_base,
    }
    # 局部渲染：仅返回日志表格和分页（AJAX 部分更新）
    if request.headers.get('X-Requested-With') == 'XMLHttpRequest' or request.GET.get('fragment') == 'logs':
        return render(request, 'assets/partials/asset_logs_table.html', ctx)
    return render(request, 'assets/asset_detail.html', ctx)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_update(request, pk):
    asset = get_object_or_404(Asset, pk=pk)
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        # 基本字段
        name = request.POST.get('name', '').strip()
        asset_number = request.POST.get('asset_number', '').strip()
        category_id = request.POST.get('category')
        status = (request.POST.get('status') or asset.status).strip()
        # 归属与关联
        organization_id = request.POST.get('organization')
        ledger_id = request.POST.get('ledger')
        department_id = request.POST.get('department')
        location_id = request.POST.get('location')
        user_id = request.POST.get('user')
        supplier_id = request.POST.get('supplier')
        contract_id = request.POST.get('contract')
        # 财务信息
        purchase_date_str = request.POST.get('purchase_date', '').strip()
        purchase_price_str = request.POST.get('purchase_price', '').strip()
        warranty_period_str = request.POST.get('warranty_period', '').strip()
        depreciation_rate_str = request.POST.get('depreciation_rate', '').strip()
        description = request.POST.get('description', '').strip()
        image_file = request.FILES.get('image')

        errors = []
        if not name:
            errors.append('资产名称为必填')
        if not asset_number:
            errors.append('资产编号为必填')
        if not category_id:
            errors.append('资产类别为必填')
        if not purchase_date_str:
            errors.append('启用日期为必填')
        if not purchase_price_str:
            errors.append('采购价格为必填')

        # 转换与校验
        try:
            purchase_date = datetime.strptime(purchase_date_str, '%Y-%m-%d').date()
        except Exception:
            errors.append('启用日期格式不正确')
            purchase_date = None
        try:
            purchase_price = Decimal(purchase_price_str)
        except Exception:
            errors.append('采购价格格式不正确')
            purchase_price = None
        warranty_period = asset.warranty_period or 0
        if warranty_period_str:
            try:
                warranty_period = int(warranty_period_str)
            except Exception:
                errors.append('保修期格式不正确')
        depreciation_rate = asset.depreciation_rate or Decimal('0')
        if depreciation_rate_str:
            try:
                depreciation_rate = Decimal(depreciation_rate_str)
            except Exception:
                errors.append('折旧率格式不正确')

        if errors:
            for e in errors:
                messages.error(request, e)
            ctx = _build_asset_form_context(asset)
            ctx.update({
                'asset': asset,
                'form_action': 'edit',
                'name': name,
                'asset_number': asset_number,
                'category_id': int(category_id) if category_id else None,
                'status': status,
                'organization_id': int(organization_id) if organization_id else None,
                'ledger_id': int(ledger_id) if ledger_id else None,
                'department_id': int(department_id) if department_id else None,
                'location_id': int(location_id) if location_id else None,
                'user_id': int(user_id) if user_id else None,
                'supplier_id': int(supplier_id) if supplier_id else None,
                'contract_id': int(contract_id) if contract_id else None,
                'purchase_date': purchase_date_str,
                'purchase_price': purchase_price_str,
                'warranty_period': warranty_period_str,
                'depreciation_rate': depreciation_rate_str,
                'description': description,
            })
            if is_ajax:
                html = render(request, 'assets/_asset_form_body.html', ctx).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html}, status=400)
            template_name = 'assets/asset_form_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_form.html'
            return render(request, template_name, ctx)

        # 更新资产
        asset.name = name
        asset.asset_number = asset_number
        asset.category_id = category_id or None
        asset.status = status or asset.status
        asset.ledger_id = ledger_id or None
        asset.department_id = department_id or None
        asset.location_id = location_id or None
        asset.user_id = user_id or None
        asset.supplier_id = supplier_id or None
        asset.contract_id = contract_id or None
        asset.purchase_date = purchase_date
        asset.purchase_price = purchase_price
        asset.warranty_period = warranty_period or 0
        asset.depreciation_rate = depreciation_rate or Decimal('0')
        asset.description = description
        asset.save()
        if image_file:
            asset.image = image_file
            asset.save(update_fields=['image'])

        if is_ajax:
            return JsonResponse({'success': True, 'message': '资产已更新', 'asset_id': asset.id})
        messages.success(request, '资产已更新')
        # 保持在当前编辑页（特别是 modal 模式下不跳转）
        if request.GET.get('modal') == '1':
            ctx = _build_asset_form_context(asset)
            ctx.update({'asset': asset, 'form_action': 'edit'})
            template_name = 'assets/asset_form_modal.html'
            return render(request, template_name, ctx)
        else:
            return redirect('assets:asset_detail', pk=asset.pk)
    ctx = _build_asset_form_context(asset)
    ctx.update({'asset': asset, 'form_action': 'edit'})
    if is_ajax:
        html = render(request, 'assets/_asset_form_body.html', ctx).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    template_name = 'assets/asset_form_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_form.html'
    return render(request, template_name, ctx)


@login_required
@permission_required('assets.delete_asset', raise_exception=True)
def asset_delete(request, pk):
    asset = get_object_or_404(Asset, pk=pk)
    if request.method == 'POST':
        name = asset.name
        asset.delete()
        messages.success(request, f'资产 {name} 已删除')
        return redirect('assets:asset_list')
    return render(request, 'assets/asset_confirm_delete.html', {'asset': asset})


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_allocate(request, pk):
    asset = get_object_or_404(Asset, pk=pk)
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        department_id = request.POST.get('department')
        user_id = request.POST.get('user')
        description = (request.POST.get('description') or '').strip()

        old_value = {
            'department_id': asset.department_id,
            'user_id': asset.user_id,
            'status': asset.status,
        }

        updates = {}
        # 分配可以只分配部门或用户，但至少需要一个
        if department_id is not None and department_id != '':
            updates['department_id'] = int(department_id)
        if user_id is not None and user_id != '':
            updates['user_id'] = int(user_id)

        error_msg = None
        if updates:
            # 分配后状态设为在用
            updates['status'] = 'in_use'
            for k, v in updates.items():
                setattr(asset, k, v)
            asset.save(update_fields=list(updates.keys()))

            new_value = {
                'department_id': asset.department_id,
                'user_id': asset.user_id,
                'status': asset.status,
            }
            AssetLog.log_action(
                asset=asset,
                action='allocate',
                operator=request.user,
                description=description or '资产分配',
                old_value=old_value,
                new_value=new_value,
                request=request,
            )
            if request.GET.get('modal') != '1' and not is_ajax:
                messages.success(request, '资产已分配，状态已设为在用')
        else:
            error_msg = '请至少选择部门或用户进行分配'
            if request.GET.get('modal') != '1' and not is_ajax:
                messages.warning(request, error_msg)
        # AJAX 返回
        if is_ajax:
            if error_msg:
                departments = Department.objects.all().order_by('name')
                users = User.objects.all().order_by('username')
                html = render(request, 'assets/actions/_allocate_form.html', {
                    'asset': asset, 'departments': departments, 'users': users, 'error': error_msg,
                }).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html, 'error': error_msg}, status=400)
            return JsonResponse({'success': True, 'message': '资产已分配，状态已设为在用', 'asset_id': asset.id})
        # 旧 modal/普通流程
        if request.GET.get('modal') == '1':
            msg = '资产已分配，状态已设为在用'
            html = f"""
            <!doctype html>
            <html><head><meta charset='utf-8'><title>成功</title>
            <script>
            try {{ window.parent && window.parent.postMessage({{type:'asset:operation-success', message:'{msg}'}}, '*'); }} catch(e){{}}
            </script>
            </head><body>
            <div style='padding:12px;font-family:sans-serif;'>操作成功，可关闭窗口</div>
            </body></html>
            """
            return HttpResponse(html)
        return redirect('assets:asset_detail', pk=pk)

    if is_ajax:
        departments = Department.objects.all().order_by('name')
        users = User.objects.all().order_by('username')
        html = render(request, 'assets/actions/_allocate_form.html', {
            'asset': asset,
            'departments': departments,
            'users': users,
        }).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    template_name = 'assets/asset_allocate_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_allocate.html'
    return render(request, template_name, {
        'asset': asset,
        'departments': Department.objects.all().order_by('name'),
        'users': User.objects.all().order_by('username'),
    })


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_transfer(request, pk):
    asset = get_object_or_404(Asset, pk=pk)
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        department_id = request.POST.get('department')
        user_id = request.POST.get('user')
        description = (request.POST.get('description') or '').strip()

        old_value = {
            'department_id': asset.department_id,
            'user_id': asset.user_id,
        }

        updates = {}
        if department_id is not None:
            updates['department_id'] = int(department_id) if department_id else None
        if user_id is not None:
            updates['user_id'] = int(user_id) if user_id else None

        error_msg = None
        if updates:
            for k, v in updates.items():
                setattr(asset, k, v)
            asset.save(update_fields=list(updates.keys()))

            new_value = {
                'department_id': asset.department_id,
                'user_id': asset.user_id,
            }
            AssetLog.log_action(
                asset=asset,
                action='transfer',
                operator=request.user,
                description=description or '资产调拨/转移',
                old_value=old_value,
                new_value=new_value,
                request=request,
            )
            if request.GET.get('modal') != '1' and not is_ajax:
                messages.success(request, '资产已转移')
        else:
            error_msg = '未提交有效的转移信息'
            if request.GET.get('modal') != '1' and not is_ajax:
                messages.warning(request, error_msg)
        # AJAX 返回
        if is_ajax:
            if error_msg:
                departments = Department.objects.all().order_by('name')
                users = User.objects.all().order_by('username')
                html = render(request, 'assets/actions/_transfer_form.html', {
                    'asset': asset, 'departments': departments, 'users': users, 'error': error_msg,
                }).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html, 'error': error_msg}, status=400)
            return JsonResponse({'success': True, 'message': '资产已转移', 'asset_id': asset.id})
        # 旧 modal/普通流程
        if request.GET.get('modal') == '1':
            msg = '资产已转移'
            html = f"""
            <!doctype html>
            <html><head><meta charset='utf-8'><title>成功</title>
            <script>
            try {{ window.parent && window.parent.postMessage({{type:'asset:operation-success', message:'{msg}'}}, '*'); }} catch(e){{}}
            </script>
            </head><body>
            <div style='padding:12px;font-family:sans-serif;'>操作成功，可关闭窗口</div>
            </body></html>
            """
            return HttpResponse(html)
        return redirect('assets:asset_detail', pk=pk)

    if is_ajax:
        departments = Department.objects.all().order_by('name')
        users = User.objects.all().order_by('username')
        html = render(request, 'assets/actions/_transfer_form.html', {
            'asset': asset,
            'departments': departments,
            'users': users,
        }).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    template_name = 'assets/asset_transfer_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_transfer.html'
    return render(request, template_name, {
        'asset': asset,
        'departments': Department.objects.all().order_by('name'),
        'users': User.objects.all().order_by('username'),
    })


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_maintenance(request, pk):
    asset = get_object_or_404(Asset, pk=pk)
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        description = (request.POST.get('description') or '').strip()

        old_value = {'status': asset.status}

        if not description:
            error_msg = '请填写维护说明'
            if is_ajax:
                html = render(request, 'assets/actions/_maintenance_form.html', {'asset': asset, 'error': error_msg}).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html, 'error': error_msg}, status=400)
            messages.error(request, error_msg)
            template_name = 'assets/asset_maintenance_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_maintenance.html'
            return render(request, template_name, {'asset': asset})

        asset.status = 'maintenance'
        asset.save(update_fields=['status'])

        AssetLog.log_action(
            asset=asset,
            action='maintenance',
            operator=request.user,
            description=description,
            old_value=old_value,
            new_value={'status': asset.status},
            request=request,
        )
        if is_ajax:
            return JsonResponse({'success': True, 'message': '资产状态已变更为维修中', 'asset_id': asset.id})
        if request.GET.get('modal') != '1':
            messages.success(request, '资产状态已变更为维修中')
        # modal 模式：返回可通知父页面的最小成功页
        if request.GET.get('modal') == '1':
            msg = '资产状态已变更为维修中'
            html = f"""
            <!doctype html>
            <html><head><meta charset='utf-8'><title>成功</title>
            <script>
            try {{ window.parent && window.parent.postMessage({{type:'asset:operation-success', message:'{msg}'}}, '*'); }} catch(e){{}}
            </script>
            </head><body>
            <div style='padding:12px;font-family:sans-serif;'>操作成功，可关闭窗口</div>
            </body></html>
            """
            return HttpResponse(html)
        return redirect('assets:asset_detail', pk=pk)

    if is_ajax:
        html = render(request, 'assets/actions/_maintenance_form.html', {'asset': asset}).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
    template_name = 'assets/asset_maintenance_modal.html' if request.GET.get('modal') == '1' else 'assets/asset_maintenance.html'
    return render(request, template_name, {'asset': asset})
@permission_required('assets.change_asset', raise_exception=True)
def asset_scrap(request, pk):
    try:
        asset = get_object_or_404(Asset, pk=pk)
        if hasattr(asset, 'status'):
            asset.status = 'scrapped'
            asset.save()
        messages.success(request, '资产已报废（占位实现）')
    except Exception:
        messages.error(request, '资产报废失败')
    return redirect('assets:asset_detail', pk=pk)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_recycle(request, pk):
    try:
        asset = get_object_or_404(Asset, pk=pk)
        # 回收：状态改为 idle，并尽量清理使用人/部门（若模型包含这些字段）
        updated_fields = []
        if hasattr(asset, 'status'):
            asset.status = 'idle'
            updated_fields.append('status')
        if hasattr(asset, 'user_id'):
            asset.user_id = None
            updated_fields.append('user_id')
        if hasattr(asset, 'user') and 'user_id' not in updated_fields:
            # 兼容无 *_id 原始字段命名
            asset.user = None
            updated_fields.append('user')
        if hasattr(asset, 'department_id'):
            asset.department_id = None
            updated_fields.append('department_id')
        if hasattr(asset, 'department') and 'department_id' not in updated_fields:
            asset.department = None
            updated_fields.append('department')
        if updated_fields:
            asset.save(update_fields=updated_fields)
        messages.success(request, '资产已回收')
    except Exception:
        messages.error(request, '资产恢复失败')
    return redirect('assets:asset_detail', pk=pk)


@login_required
@permission_required('assets.add_asset', raise_exception=True)
def bulk_import_assets_simple(request):
    """Deprecated: simplified legacy importer. Not wired in URLs.
    Use the enhanced `bulk_import_assets` view defined later in this file.
    """
    if request.method == 'POST' and request.FILES.get('excel_file'):
        success, errors = 0, 0
        try:
            wb = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = wb.active
            for row in sheet.iter_rows(min_row=2, values_only=True):
                if not row or not row[0] or not row[1]:
                    errors += 1
                    continue
                name = str(row[0]).strip()
                code = str(row[1]).strip()
                if Asset.objects.filter(code=code).exists():
                    errors += 1
                    continue
                Asset.objects.create(name=name, code=code)
                success += 1
            if success:
                messages.success(request, f'成功导入 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录导入失败或重复')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
        # 成功导入后返回资产列表
        if success > 0:
            return redirect('assets:asset_list')
    return render(request, 'assets/bulk_import.html', {'import_type': 'assets', 'title': '批量导入资产'})


@login_required
@permission_required('assets.delete_asset', raise_exception=True)
def asset_batch_delete(request):
    if request.method == 'POST':
        # 兼容前端表单 name="asset_ids" 与 后端原 name="ids"
        ids = request.POST.getlist('ids') or request.POST.getlist('asset_ids')
        qs = Asset.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个资产')
        return redirect('assets:asset_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_batch_update_status(request):
    if request.method == 'POST':
        # 兼容前端表单字段
        ids = request.POST.getlist('ids') or request.POST.getlist('asset_ids')
        status_val = (request.POST.get('new_status') or request.POST.get('status') or '').strip()
        if not status_val:
            return JsonResponse({'success': False, 'error': '缺少状态值'}, status=400)
        updated = Asset.objects.filter(id__in=ids)
        if hasattr(Asset, 'status'):
            updated.update(status=status_val)
        messages.success(request, f'已更新 {updated.count()} 个资产状态')
        return redirect('assets:asset_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


@login_required
@permission_required('assets.change_asset', raise_exception=True)
def asset_batch_recycle(request):
    if request.method == 'POST':
        # 兼容前端表单字段
        ids = request.POST.getlist('ids') or request.POST.getlist('asset_ids')
        qs = Asset.objects.filter(id__in=ids)
        # 批量回收：状态改为 idle，并尽量清理使用人/部门（若模型包含这些字段）
        field_names = {f.name for f in Asset._meta.get_fields()}
        update_kwargs = {}
        if 'status' in field_names:
            update_kwargs['status'] = 'idle'
        if 'user' in field_names:
            update_kwargs['user'] = None
        if 'department' in field_names:
            update_kwargs['department'] = None
        if update_kwargs:
            qs.update(**update_kwargs)
        messages.success(request, f'已回收 {qs.count()} 个资产，且已清空使用人/部门（如适用）')
        return redirect('assets:asset_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

# ==================== 供应商管理视图（最小实现） ====================
@login_required
@permission_required('assets.view_supplier', raise_exception=True)
def supplier_list(request):
    q = request.GET.get('q', '').strip()
    qs = Supplier.objects.all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(contact_person__icontains=q))
    paginator = Paginator(qs.order_by('-created_at'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/supplier_list.html', {'page_obj': page_obj, 'query': q})


@login_required
@permission_required('assets.add_supplier', raise_exception=True)
def supplier_create(request):
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        if not name:
            messages.error(request, '供应商名称不能为空')
        else:
            Supplier.objects.create(name=name)
            messages.success(request, '供应商创建成功')
            return redirect('assets:supplier_list')
    return render(request, 'assets/supplier_form.html', {'form_action': 'create'})


@login_required
@permission_required('assets.view_supplier', raise_exception=True)
def supplier_detail(request, pk):
    supplier = get_object_or_404(Supplier, pk=pk)
    # 提供资质文件上传表单到模板以渲染选择框等控件
    upload_form = SupplierDocumentForm()
    return render(request, 'assets/supplier_detail.html', {
        'supplier': supplier,
        'upload_form': upload_form,
    })


@login_required
@permission_required('assets.change_supplier', raise_exception=True)
def supplier_update(request, pk):
    supplier = get_object_or_404(Supplier, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        if not name:
            messages.error(request, '供应商名称不能为空')
        else:
            supplier.name = name
            supplier.save()
            messages.success(request, '供应商信息已更新')
            return redirect('assets:supplier_detail', pk=supplier.pk)
    return render(request, 'assets/supplier_form.html', {'supplier': supplier, 'form_action': 'edit'})


@login_required
@permission_required('assets.delete_supplier', raise_exception=True)
def supplier_delete(request, pk):
    supplier = get_object_or_404(Supplier, pk=pk)
    if request.method == 'POST':
        name = supplier.name
        supplier.delete()
        messages.success(request, f'供应商 {name} 已删除')
        return redirect('assets:supplier_list')
    return render(request, 'assets/supplier_confirm_delete.html', {'supplier': supplier})


@login_required
@permission_required('assets.add_supplier', raise_exception=True)
def supplier_bulk_import(request):
    if request.method == 'POST' and request.FILES.get('excel_file'):
        try:
            wb = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = wb.active
            success, errors = 0, 0
            for row in sheet.iter_rows(min_row=2, values_only=True):
                if not row or not row[0]:
                    errors += 1
                    continue
                name = str(row[0]).strip()
                if Supplier.objects.filter(name=name).exists():
                    errors += 1
                    continue
                Supplier.objects.create(name=name)
                success += 1
            if success:
                messages.success(request, f'成功导入 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录导入失败或重复')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
    return render(request, 'assets/bulk_import.html', {'import_type': 'suppliers', 'title': '批量导入供应商'})


@login_required
@permission_required('assets.delete_supplier', raise_exception=True)
def supplier_batch_delete(request):
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = Supplier.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个供应商')
        return redirect('assets:supplier_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


@login_required
@permission_required('assets.add_supplier', raise_exception=True)
def supplier_upload_document(request, pk):
    supplier = get_object_or_404(Supplier, pk=pk)
    if request.method == 'POST':
        file = request.FILES.get('file')
        title = request.POST.get('title', '')
        if file and title:
            SupplierDocument.objects.create(supplier=supplier, document_type='other', title=title, file=file, uploaded_by=request.user)
            messages.success(request, '文件上传成功')
            return redirect('assets:supplier_detail', pk=supplier.pk)
        messages.error(request, '请选择文件并填写标题')
    return render(request, 'assets/supplier_upload_document.html', {'supplier': supplier})


@login_required
@permission_required('assets.add_supplier', raise_exception=True)
def supplier_qualification_upload(request):
    if request.method == 'POST':
        supplier_id = request.POST.get('supplier_id')
        file = request.FILES.get('file')
        title = request.POST.get('title', '资质文件')
        supplier = get_object_or_404(Supplier, pk=supplier_id)
        if file:
            SupplierDocument.objects.create(supplier=supplier, document_type='qualification', title=title, file=file, uploaded_by=request.user)
            return JsonResponse({'success': True})
    return JsonResponse({'success': False}, status=400)


@login_required
@permission_required('assets.delete_supplier', raise_exception=True)
def supplier_qualification_delete(request, file_id):
    doc = get_object_or_404(SupplierDocument, pk=file_id)
    doc.delete()
    messages.success(request, '资质文件已删除')
    return redirect('assets:supplier_detail', pk=doc.supplier_id)

 
# ==================== 合同管理视图（最小实现） ====================
@login_required
@permission_required('assets.view_contract', raise_exception=True)
def contract_list(request):
    q = request.GET.get('q', '').strip()
    qs = Contract.objects.select_related('supplier').all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(contract_number__icontains=q) | Q(supplier__name__icontains=q))
    paginator = Paginator(qs.order_by('-created_at'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/contract_list.html', {'page_obj': page_obj, 'query': q})


@login_required
@permission_required('assets.add_contract', raise_exception=True)
def contract_create(request):
    # 仅渲染表单页；必填字段较多，避免此处简化创建导致数据不完整
    if request.method == 'POST':
        messages.error(request, '请使用完整的合同创建流程')
    return render(request, 'assets/contract_form.html', {'suppliers': Supplier.objects.all(), 'form_action': 'create'})


@login_required
@permission_required('assets.view_contract', raise_exception=True)
def contract_detail(request, pk):
    contract = get_object_or_404(Contract, pk=pk)
    return render(request, 'assets/contract_detail.html', {'contract': contract})


@login_required
@permission_required('assets.view_contract', raise_exception=True)
def contract_file_view(request, file_id):
    """安全在线查看合同文件。
    - 仅允许已登录且具有查看合同权限的用户访问
    - PDF 使用 inline 在浏览器中打开，其它类型默认为下载
    """
    from django.http import FileResponse, Http404
    import mimetypes
    import os

    cf = get_object_or_404(ContractFile, pk=file_id)
    if not cf.file_path:
        raise Http404('文件不存在')

    file_path = cf.file_path.path
    if not os.path.exists(file_path):
        raise Http404('文件不存在')

    content_type, _ = mimetypes.guess_type(file_path)

    filename = cf.file_name or os.path.basename(file_path)

    if content_type == 'application/pdf':
        response = FileResponse(open(file_path, 'rb'), content_type=content_type, filename=filename)
        response['Content-Disposition'] = f'inline; filename="{filename}"'
    else:
        response = FileResponse(open(file_path, 'rb'), content_type=content_type or 'application/octet-stream', filename=filename)
        response['Content-Disposition'] = f'attachment; filename="{filename}"'

    return response


@login_required
@require_POST
def contract_submit_review(request, pk):
    """提交合同审核（AJAX JSON）"""
    from django.http import JsonResponse
    import json
    contract = get_object_or_404(Contract, pk=pk)

    try:
        payload = json.loads(request.body.decode('utf-8') or '{}')
    except json.JSONDecodeError:
        payload = {}

    notes = (payload.get('review_notes') or '').strip()

    # 记录一条提交审核的审核记录
    try:
        ContractReview.objects.create(
            contract=contract,
            review_type='submit',
            reviewer=request.user,  # 作为提交人记录
            review_notes=notes,
        )

        # 同步更新合同上的审核备注（不改变状态，保持草稿，后续由审核流改变）
        if notes:
            contract.review_notes = notes
            contract.save(update_fields=['review_notes'])

        return JsonResponse({'success': True, 'message': '已提交审核'})
    except Exception as e:
        return JsonResponse({'success': False, 'error': f'提交失败：{e}'}, status=400)


@login_required
@permission_required('assets.change_contract', raise_exception=True)
def contract_update(request, pk):
    contract = get_object_or_404(Contract, pk=pk)
    if request.method == 'POST':
        messages.error(request, '请使用完整的合同编辑流程')
    return render(request, 'assets/contract_form.html', {'contract': contract, 'suppliers': Supplier.objects.all(), 'form_action': 'edit'})


@login_required
@permission_required('assets.delete_contract', raise_exception=True)
def contract_delete(request, pk):
    contract = get_object_or_404(Contract, pk=pk)
    if request.method == 'POST':
        name = contract.name
        contract.delete()
        messages.success(request, f'合同 {name} 已删除')
        return redirect('assets:contract_list')
    return render(request, 'assets/contract_confirm_delete.html', {'contract': contract})


@login_required
@permission_required('assets.add_contract', raise_exception=True)
def contract_bulk_import(request):
    if request.method == 'POST' and request.FILES.get('excel_file'):
        try:
            wb = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = wb.active
            success, errors = 0, 0
            for row in sheet.iter_rows(min_row=2, values_only=True):
                # 仅统计占位，实际导入逻辑后续完善
                if row and row[0]:
                    success += 1
                else:
                    errors += 1
            if success:
                messages.success(request, f'读取 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录缺少必填项')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
    return render(request, 'assets/bulk_import.html', {'import_type': 'contracts', 'title': '批量导入合同'})


@login_required
@permission_required('assets.delete_contract', raise_exception=True)
def contract_batch_delete(request):
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = Contract.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 份合同')
        return redirect('assets:contract_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)


@login_required
@permission_required('assets.add_contract', raise_exception=True)
def contract_upload_file(request, pk):
    contract = get_object_or_404(Contract, pk=pk)
    if request.method == 'POST':
        file = request.FILES.get('file')
        title = request.POST.get('title', '')
        file_type = request.POST.get('file_type', 'attachment')
        if file and title:
            ContractFile.objects.create(contract=contract, file_type=file_type, file_name=title, file_path=file, uploaded_by=request.user, file_size=getattr(file, 'size', 0))
            messages.success(request, '文件上传成功')
            return redirect('assets:contract_detail', pk=contract.pk)
        messages.error(request, '请选择文件并填写名称')
    return render(request, 'assets/contract_upload_file.html', {'contract': contract})


@login_required
@permission_required('assets.delete_contract', raise_exception=True)
def contract_delete_file(request, file_id):
    cf = get_object_or_404(ContractFile, pk=file_id)
    contract_id = cf.contract_id
    cf.delete()
    messages.success(request, '文件已删除')
    return redirect('assets:contract_detail', pk=contract_id)


# ==================== 仓库管理视图（最小实现） ====================
@login_required
@permission_required('assets.view_warehouse', raise_exception=True)
def warehouse_list(request):
    q = request.GET.get('q', '').strip()
    qs = Warehouse.objects.all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(code__icontains=q))
    paginator = Paginator(qs.order_by('name'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/warehouse_list.html', {'page_obj': page_obj, 'query': q})


@login_required
@permission_required('assets.add_warehouse', raise_exception=True)
def warehouse_create(request):
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    # 基础数据
    locations = Location.objects.all().order_by('name')
    users = User.objects.all().order_by('username')
    
    if request.method == 'POST':
        name = (request.POST.get('name') or '').strip()
        location_id = (request.POST.get('location') or '').strip()
        manager_id = (request.POST.get('manager') or '').strip()
        description = (request.POST.get('description') or '').strip()
        
        # 验证必填字段
        errors = []
        if not name:
            errors.append('名称为必填')
        if not location_id:
            errors.append('位置为必填')
            
        if errors:
            if is_ajax:
                html = render(request, 'assets/partials/warehouse_form_fields.html', {
                    'name': name,
                    'location_id': location_id,
                    'manager_id': manager_id,
                    'description': description,
                    'errors': errors,
                    'locations': locations,
                    'users': users,
                }).content.decode('utf-8')
                return JsonResponse({'success': False, 'html': html}, status=400)
            messages.error(request, '，'.join(errors))
            return render(request, 'assets/warehouse_form.html', {
                'form_action': 'create',
                'locations': locations,
                'users': users,
                'name': name,
                'location_id': location_id,
                'manager_id': manager_id,
                'description': description,
                'errors': errors,
            })
            
        try:
            with transaction.atomic():
                # 使用 select_for_update 锁定相关行，防止并发问题
                location = Location.objects.select_for_update().get(pk=location_id)
                
                # 线程安全地创建仓库（如果已存在会引发 IntegrityError）
                warehouse, created = Warehouse.objects.get_or_create(
                    name=name,
                    location=location,
                    defaults={
                        'manager_id': manager_id or None,
                        'description': description,
                    }
                )
                
                if not created:
                    if is_ajax:
                        return JsonResponse({
                            'success': True, 
                            'duplicate': True, 
                            'id': warehouse.id,
                            'message': '已存在同名且同位置的仓库，未重复创建'
                        })
                    messages.info(request, '已存在同名且同位置的仓库，未重复创建')
                    return redirect('assets:warehouse_list')
                
                if is_ajax:
                    return JsonResponse({
                        'success': True,
                        'id': warehouse.id,
                        'message': '仓库创建成功'
                    })
                messages.success(request, '仓库创建成功')
                return redirect('assets:warehouse_detail', pk=warehouse.pk)
                
        except IntegrityError as e:
            logger.error(f"创建仓库时发生数据库错误: {e}")
            error_msg = '创建仓库时发生错误，请稍后重试'
            if 'unique_warehouse_location' in str(e):
                error_msg = '已存在同名且同位置的仓库'
                
            if is_ajax:
                return JsonResponse({
                    'success': False, 
                    'error': error_msg
                }, status=400)
                
            messages.error(request, error_msg)
            return render(request, 'assets/warehouse_form.html', {
                'form_action': 'create',
                'locations': locations,
                'users': users,
                'name': name,
                'location_id': location_id,
                'manager_id': manager_id,
                'description': description,
                'errors': [error_msg],
            })
            
        except Exception as e:
            logger.exception("创建仓库时发生未预期的错误")
            error_msg = '创建仓库时发生错误，请稍后重试'
            if is_ajax:
                return JsonResponse({
                    'success': False, 
                    'error': error_msg
                }, status=500)
                
            messages.error(request, error_msg)
            return render(request, 'assets/warehouse_form.html', {
                'form_action': 'create',
                'locations': locations,
                'users': users,
                'name': name,
                'location_id': location_id,
                'manager_id': manager_id,
                'description': description,
                'errors': [error_msg],
            })
    
    # GET 请求处理
    if is_ajax:
        html = render(request, 'assets/partials/warehouse_form_fields.html', {
            'name': '',
            'location_id': '',
            'manager_id': '',
            'description': '',
            'errors': [],
            'locations': locations,
            'users': users,
        }).content.decode('utf-8')
        return JsonResponse({'success': True, 'html': html})
        
    return render(request, 'assets/warehouse_form.html', {
        'form_action': 'create',
        'locations': locations,
        'users': users,
    })


@login_required
@permission_required('assets.view_warehouse', raise_exception=True)
def warehouse_detail(request, pk):
    warehouse = get_object_or_404(Warehouse, pk=pk)
    return render(request, 'assets/warehouse_detail.html', {'warehouse': warehouse})


@login_required
@permission_required('assets.change_warehouse', raise_exception=True)
def warehouse_update(request, pk):
    warehouse = get_object_or_404(Warehouse, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        if not name or not code:
            messages.error(request, '名称与代码为必填')
        else:
            warehouse.name = name
            warehouse.code = code
            warehouse.save()
            messages.success(request, '仓库信息已更新')
            return redirect('assets:warehouse_detail', pk=warehouse.pk)
    return render(request, 'assets/warehouse_form.html', {'warehouse': warehouse, 'form_action': 'edit'})


@login_required
@permission_required('assets.delete_warehouse', raise_exception=True)
def warehouse_delete(request, pk):
    warehouse = get_object_or_404(Warehouse, pk=pk)
    if request.method == 'POST':
        name = warehouse.name
        warehouse.delete()
        messages.success(request, f'仓库 {name} 已删除')
        return redirect('assets:warehouse_list')
    return render(request, 'assets/warehouse_confirm_delete.html', {'warehouse': warehouse})


@login_required
@permission_required('assets.delete_warehouse', raise_exception=True)
def warehouse_batch_delete(request):
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = Warehouse.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个仓库')
        return redirect('assets:warehouse_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

@login_required
@permission_required('assets.view_location', raise_exception=True)
def location_list(request):
    """位置列表"""
    q = request.GET.get('q', '').strip()
    qs = Location.objects.all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(address__icontains=q))
    paginator = Paginator(qs.order_by('name'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/location_list.html', {
        'page_obj': page_obj,
        'query': q,
    })


@login_required
@permission_required('assets.add_location', raise_exception=True)
def location_create(request):
    """创建位置"""
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        address = request.POST.get('address', '').strip()
        if not name:
            messages.error(request, '位置名称不能为空')
        else:
            Location.objects.create(name=name, address=address)
            messages.success(request, '位置创建成功')
            return redirect('assets:location_list')
    return render(request, 'assets/location_form.html', {
        'form_action': 'create',
    })


@login_required
@permission_required('assets.view_location', raise_exception=True)
def location_detail(request, pk):
    """位置详情"""
    loc = get_object_or_404(Location, pk=pk)
    return render(request, 'assets/location_detail.html', {
        'location': loc,
    })


@login_required
@permission_required('assets.change_location', raise_exception=True)
def location_update(request, pk):
    """编辑位置"""
    loc = get_object_or_404(Location, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        address = request.POST.get('address', '').strip()
        if not name:
            messages.error(request, '位置名称不能为空')
        else:
            loc.name = name
            loc.address = address
            loc.save()
            messages.success(request, '位置信息已更新')
            return redirect('assets:location_detail', pk=loc.pk)
    return render(request, 'assets/location_form.html', {
        'location': loc,
        'form_action': 'edit',
    })


@login_required
@permission_required('assets.delete_location', raise_exception=True)
def location_delete(request, pk):
    """删除位置"""
    loc = get_object_or_404(Location, pk=pk)
    if request.method == 'POST':
        name = loc.name
        loc.delete()
        messages.success(request, f'位置 {name} 已删除')
        return redirect('assets:location_list')
    return render(request, 'assets/location_confirm_delete.html', {'location': loc})


@login_required
@permission_required('assets.view_department', raise_exception=True)
def department_list(request):
    """部门列表"""
    q = request.GET.get('q', '').strip()
    qs = Department.objects.select_related('organization', 'location', 'parent').all()
    if q:
        qs = qs.filter(Q(name__icontains=q) | Q(code__icontains=q) | Q(organization__name__icontains=q))
    paginator = Paginator(qs.order_by('name'), 20)
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'assets/department_list.html', {
        'page_obj': page_obj,
        'query': q,
    })


@login_required
@permission_required('assets.add_department', raise_exception=True)
def department_create(request):
    """创建部门"""
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        org_id = request.POST.get('organization')
        loc_id = request.POST.get('location')
        parent_id = request.POST.get('parent')
        if not name or not code or not org_id:
            messages.error(request, '名称、代码和所属组织为必填')
        else:
            organization = Organization.objects.filter(pk=org_id).first()
            if not organization:
                messages.error(request, '请选择有效的所属组织')
            else:
                location = Location.objects.filter(pk=loc_id).first() if loc_id else None
                parent = Department.objects.filter(pk=parent_id).first() if parent_id else None
                Department.objects.create(name=name, code=code, organization=organization, location=location, parent=parent)
                messages.success(request, '部门创建成功')
                return redirect('assets:department_list')
    return render(request, 'assets/department_form.html', {
        'organizations': Organization.objects.all(),
        'locations': Location.objects.all(),
        'departments': Department.objects.all(),
        'form_action': 'create',
    })


@login_required
@permission_required('assets.view_department', raise_exception=True)
def department_detail(request, pk):
    """部门详情"""
    dept = get_object_or_404(Department, pk=pk)
    children = Department.objects.filter(parent=dept)
    return render(request, 'assets/department_detail.html', {
        'department': dept,
        'children': children,
    })


@login_required
@permission_required('assets.change_department', raise_exception=True)
def department_update(request, pk):
    """编辑部门"""
    dept = get_object_or_404(Department, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        code = request.POST.get('code', '').strip()
        org_id = request.POST.get('organization')
        loc_id = request.POST.get('location')
        parent_id = request.POST.get('parent')
        if not name or not code or not org_id:
            messages.error(request, '名称、代码和所属组织为必填')
        else:
            organization = Organization.objects.filter(pk=org_id).first()
            if not organization:
                messages.error(request, '请选择有效的所属组织')
            else:
                location = Location.objects.filter(pk=loc_id).first() if loc_id else None
                parent = Department.objects.filter(pk=parent_id).first() if parent_id else None
                dept.name = name
                dept.code = code
                dept.organization = organization
                dept.location = location
                dept.parent = parent
                dept.save()
                messages.success(request, '部门信息已更新')
                return redirect('assets:department_detail', pk=dept.pk)
    return render(request, 'assets/department_form.html', {
        'department': dept,
        'organizations': Organization.objects.all(),
        'locations': Location.objects.all(),
        'departments': Department.objects.exclude(pk=dept.pk),
        'form_action': 'edit',
    })


@login_required
@permission_required('assets.delete_department', raise_exception=True)
def department_delete(request, pk):
    """删除部门"""
    dept = get_object_or_404(Department, pk=pk)
    if request.method == 'POST':
        name = dept.name
        dept.delete()
        messages.success(request, f'部门 {name} 已删除')
        return redirect('assets:department_list')
    return render(request, 'assets/department_confirm_delete.html', {'department': dept})


@login_required
@permission_required('assets.add_department', raise_exception=True)
def bulk_import_departments(request):
    """批量导入部门：期望列[名称, 代码, 所属组织, 位置(可选), 上级部门(可选)]"""
    if request.method == 'POST' and request.FILES.get('excel_file'):
        try:
            wb = load_workbook(filename=BytesIO(request.FILES['excel_file'].read()))
            sheet = wb.active
            success, errors = 0, 0
            for row in sheet.iter_rows(min_row=2, values_only=True):
                if not row or not row[0] or not row[1] or not row[2]:
                    errors += 1
                    continue
                name = str(row[0]).strip()
                code = str(row[1]).strip()
                org_name = str(row[2]).strip()
                loc_name = str(row[3]).strip() if len(row) > 3 and row[3] else None
                parent_name = str(row[4]).strip() if len(row) > 4 and row[4] else None
                org = Organization.objects.filter(name=org_name).first()
                if not org:
                    errors += 1
                    continue
                loc = Location.objects.filter(name=loc_name).first() if loc_name else None
                parent = Department.objects.filter(name=parent_name, organization=org).first() if parent_name else None
                Department.objects.create(name=name, code=code, organization=org, location=loc, parent=parent)
                success += 1
            if success:
                messages.success(request, f'成功导入 {success} 条记录')
            if errors:
                messages.error(request, f'{errors} 条记录导入失败或缺少必填项')
        except Exception as e:
            messages.error(request, f'导入失败：{e}')
    return render(request, 'assets/bulk_import.html', {
        'import_type': 'departments',
        'title': '批量导入部门',
    })


@login_required
@permission_required('assets.delete_department', raise_exception=True)
def department_batch_delete(request):
    """批量删除部门"""
    if request.method == 'POST':
        ids = request.POST.getlist('ids')
        qs = Department.objects.filter(id__in=ids)
        count = qs.count()
        qs.delete()
        messages.success(request, f'已删除 {count} 个部门')
        return redirect('assets:department_list')
    return JsonResponse({'success': False, 'error': 'Invalid request'}, status=400)

@login_required
def download_import_template(request, template_type: str):
    """下载导入模板。
    - assets: 动态生成包含“所属组织*”列的资产导入模板
    - other types: 从 `templates/import_templates/` 目录提供静态模板
    """
    if template_type == 'assets':
        wb = Workbook()
        ws = wb.active
        ws.title = '资产导入模板'
        headers = [
            '资产名称*', '资产编号*', '资产类别*', '所属组织*',
            '所属部门', '使用人', '位置', '供应商', '采购日期*', '采购价格*',
            '保修期(月)', '折旧率(%)', '状态', '描述'
        ]
        ws.append(headers)
        # 表头样式
        for cell in ws[1]:
            cell.font = Font(bold=True)
            cell.alignment = Alignment(horizontal='center')
        # 示例行
        ws.append(['示例：笔记本电脑', 'NB-0001', '计算机设备', '总部', '研发部', '张三', '5楼A区', '联想', '2024-01-15', 6500, 12, 20, '在用', '2024年购入'])

        bio = BytesIO()
        wb.save(bio)
        bio.seek(0)
        response = HttpResponse(
            bio.getvalue(),
            content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        )
        response['Content-Disposition'] = 'attachment; filename="资产导入模板.xlsx"'
        return response

    # 其他类型: 静态模板
    template_files = {
        'categories': '资产类别导入模板.xlsx',
        'consumables': '耗材导入模板.xlsx',
        'organizations': '组织导入模板.xlsx',
        'departments': '部门导入模板.xlsx',
        'suppliers': '供应商导入模板.xlsx',
        'users': '用户导入模板.xlsx',
    }

    filename = template_files.get(template_type)
    if not filename:
        raise Http404('模板文件不存在')

    template_dir = os.path.join(settings.BASE_DIR, 'templates', 'import_templates')
    file_path = os.path.join(template_dir, filename)
    if not os.path.exists(file_path):
        raise Http404('模板文件不存在')

    return FileResponse(open(file_path, 'rb'), as_attachment=True, filename=filename)


@login_required
def bulk_import_assets(request):
    """高效稳定的批量导入资产视图"""
    categories = AssetCategory.objects.all()
    
    if request.method == 'POST' and request.FILES.get('excel_file'):
        excel_file = request.FILES['excel_file']
        
        # 检查文件类型
        if not excel_file.name.endswith(('.xlsx', '.xls')):
            messages.error(request, '请上传Excel文件(.xlsx或.xls)')
            return render(request, 'assets/bulk_import.html', {
                'import_type': 'assets',
                'title': '批量导入资产',
                'categories': categories
            })
        
        try:
            # 读取Excel文件
            workbook = load_workbook(filename=BytesIO(excel_file.read()))
            worksheet = workbook.active
            
            # 跳过标题行，从第二行开始读取数据
            success_count = 0
            error_count = 0
            
            for row in worksheet.iter_rows(min_row=2, values_only=True):
                if not row[0]:  # 如果名称为空，跳过该行
                    continue
                    
                try:
                    # 新模板格式（含所属组织）
                    # 列1: 资产名称* (必填)
                    name = str(row[0]).strip() if row[0] else ''
                    # 列2: 资产编号* (必填)
                    asset_number = str(row[1]).strip() if len(row) > 1 and row[1] else ''
                    # 列3: 资产类别* (必填)
                    category_name = str(row[2]).strip() if len(row) > 2 and row[2] else ''
                    # 列4: 所属组织* (必填)
                    org_name = str(row[3]).strip() if len(row) > 3 and row[3] else ''
                    
                    # 验证必填字段
                    if not name:
                        error_count += 1
                        continue
                    
                    if not asset_number:
                        error_count += 1
                        continue
                    if not category_name:
                        error_count += 1
                        continue
                    if not org_name:
                        error_count += 1
                        continue
                    
                    # 检查资产编号重复
                    if Asset.objects.filter(asset_number=asset_number).exists():
                        error_count += 1
                        continue
                    
                    # 处理资产类别
                    category = AssetCategory.objects.filter(name=category_name).first()
                    if not category:
                        error_count += 1
                        continue
                    
                    # 组织与账簿（根据组织设置默认账簿）
                    from assets.models import Organization as OrgModel, AssetLedger
                    organization = OrgModel.objects.filter(name=org_name).first()
                    if not organization:
                        error_count += 1
                        continue
                    default_ledger, _created = AssetLedger.objects.get_or_create(
                        organization=organization,
                        code='DEFAULT',
                        defaults={'name': '默认账簿', 'active': True}
                    )
                    
                    # 列5: 所属部门 (可选)
                    department = None
                    if len(row) > 4 and row[4]:
                        department_name = str(row[4]).strip()
                        if department_name:
                            try:
                                department = Department.objects.get(name=department_name)
                            except Department.DoesNotExist:
                                # 创建新部门或使用默认组织
                                default_org = organization
                                if default_org:
                                    department, created = Department.objects.get_or_create(
                                        name=department_name,
                                        defaults={
                                            'name': department_name,
                                            'organization': default_org
                                        }
                                    )
                    
                    # 列6: 使用人 (可选)
                    user = None
                    if len(row) > 5 and row[5]:
                        user_name = str(row[5]).strip()
                        if user_name:
                            try:
                                # 1. 精确匹配用户名
                                user = User.objects.filter(username=user_name).first()
                                
                                if not user:
                                    # 2. 精确匹配姓名
                                    user = User.objects.filter(
                                        models.Q(first_name=user_name) |
                                        models.Q(last_name=user_name)
                                    ).first()
                                
                                if not user:
                                    # 3. 全名匹配（处理空格问题）
                                    for u in User.objects.all():
                                        # 匹配 "姓 名" 格式
                                        if u.get_full_name().replace(' ', '') == user_name:
                                            user = u
                                            break
                                        # 匹配 "名姓" 格式
                                        if f"{u.first_name}{u.last_name}" == user_name:
                                            user = u
                                            break
                                        # 匹配 "姓名" 格式
                                        if f"{u.last_name}{u.first_name}" == user_name:
                                            user = u
                                            break
                                
                                if not user:
                                    # 4. 模糊匹配
                                    user = User.objects.filter(
                                        models.Q(username__icontains=user_name) |
                                        models.Q(first_name__icontains=user_name) |
                                        models.Q(last_name__icontains=user_name)
                                    ).first()
                            except:
                                pass
                    
                    # 列7: 位置 (可选)
                    location = None
                    if len(row) > 6 and row[6]:
                        location_name = str(row[6]).strip()
                        if location_name:
                            location, created = Location.objects.get_or_create(
                                name=location_name,
                                defaults={'name': location_name}
                            )
                    
                    # 列8: 供应商 (可选)
                    supplier = None
                    if len(row) > 7 and row[7]:
                        supplier_name = str(row[7]).strip()
                        if supplier_name:
                            supplier, created = Supplier.objects.get_or_create(
                                name=supplier_name,
                                defaults={'name': supplier_name}
                            )
                    
                    # 列9: 采购日期* (必填)
                    purchase_date = timezone.now().date()
                    if len(row) > 8 and row[8] is not None:
                        try:
                            from datetime import datetime, date
                            date_raw = row[8]
                            
                            if isinstance(date_raw, str):
                                date_str = date_raw.strip()
                                if date_str:
                                    # 尝试常用日期格式
                                    date_formats = ['%Y-%m-%d', '%Y/%m/%d', '%d/%m/%Y', '%d-%m-%Y']
                                    for fmt in date_formats:
                                        try:
                                            purchase_date = datetime.strptime(date_str, fmt).date()
                                            break
                                        except ValueError:
                                            continue
                                    else:
                                        error_count += 1
                                        continue
                            elif isinstance(date_raw, (datetime, date)):
                                purchase_date = date_raw.date() if isinstance(date_raw, datetime) else date_raw
                        except Exception:
                            error_count += 1
                            continue
                    
                    # 列10: 采购价格* (必填)
                    purchase_price = 0.0
                    if len(row) > 9 and row[9] is not None:
                        try:
                            if isinstance(row[9], str):
                                price_str = str(row[9]).strip().replace('￥', '').replace('¥', '').replace(',', '').replace('元', '')
                                if price_str:
                                    purchase_price = float(price_str)
                            else:
                                purchase_price = float(row[9])
                        except (ValueError, TypeError):
                            error_count += 1
                            continue
                    
                    # 列11: 保修期(月) (可选)
                    warranty_period = 0
                    if len(row) > 10 and row[10] is not None:
                        try:
                            if isinstance(row[10], str):
                                warranty_str = str(row[10]).strip()
                                if warranty_str:
                                    warranty_period = int(float(warranty_str))
                            else:
                                warranty_period = int(float(row[10]))
                            
                            if warranty_period < 0:
                                warranty_period = 0
                                error_count += 1
                                continue
                        except (ValueError, TypeError):
                            error_count += 1
                            continue
                    
                    # 列12: 折旧率(%) (可选)
                    depreciation_rate = 0.0
                    if len(row) > 11 and row[11] is not None:
                        try:
                            if isinstance(row[11], str):
                                rate_str = str(row[11]).strip().replace('%', '')
                                if rate_str:
                                    depreciation_rate = float(rate_str)
                            else:
                                depreciation_rate = float(row[11])
                            
                            if depreciation_rate < 0:
                                depreciation_rate = 0.0
                                error_count += 1
                                continue
                            elif depreciation_rate > 100:
                                depreciation_rate = 100.0
                                error_count += 1
                                continue
                        except (ValueError, TypeError):
                            error_count += 1
                            continue
                    
                    # 列13: 状态 (可选)
                    status = 'idle'  # 默认状态
                    if len(row) > 12 and row[12]:
                        status_str = str(row[12]).strip()
                        # 状态映射
                        status_mapping = {
                            '在用': 'in_use',
                            '闲置': 'idle', 
                            '维修': 'maintenance',
                            '报废': 'scrapped',
                            '借出': 'borrowed'
                        }
                        status = status_mapping.get(status_str, 'idle')
                    
                    # 列14: 描述 (可选)
                    description = str(row[13]).strip() if len(row) > 13 and row[13] else ''
                    
                    # 准备创建资产
                    asset_data = {
                        'name': name,
                        'asset_number': asset_number,
                        'category': category,
                        'ledger': default_ledger,
                        'department': department,
                        'user': user,
                        'location': location,
                        'supplier': supplier,
                        'purchase_date': purchase_date,
                        'purchase_price': purchase_price,
                        'warranty_period': warranty_period,
                        'depreciation_rate': depreciation_rate,
                        'status': status,
                        'description': description
                    }
                    
                    # 创建新资产
                    asset = Asset.objects.create(**asset_data)
                    success_count += 1
                    
                    # 记录批量导入日志
                    AssetLog.log_action(
                        asset=asset,
                        action='create',
                        operator=request.user,
                        description=f'批量导入创建资产 - {asset.name}',
                        request=request,
                        new_value={
                            'source': 'bulk_import',
                            'import_time': timezone.now().isoformat(),
                            'asset_data': {
                                'name': asset.name,
                                'asset_number': asset.asset_number,
                                'category': asset.category.name,
                                'purchase_price': float(asset.purchase_price),
                                'warranty_period': asset.warranty_period,
                                'depreciation_rate': float(asset.depreciation_rate)
                            }
                        }
                    )
                except Exception as e:
                    error_count += 1
            
            # 显示导入结果
            result_messages = []
            if success_count > 0:
                result_messages.append(f'成功导入 {success_count} 条记录')
            if error_count > 0:
                result_messages.append(f'{error_count} 条记录导入失败')
            
            if success_count > 0:
                messages.success(request, f'导入完成！{", ".join(result_messages)}。')
            
            # 显示详细错误信息（限制数量）
            if error_count > 0:
                error_msg = '详细信息：'
                displayed_errors = [f'第{i+2}行：数据处理错误' for i in range(error_count)]
                for detail in displayed_errors[:10]:  # 只显示前10个
                    error_msg += f'\n• {detail}'
                if len(displayed_errors) > 10:
                    error_msg += f'\n... 还有 {len(displayed_errors) - 10} 个问题'
                
                messages.error(request, error_msg)
                
        except Exception as e:
            messages.error(request, f'导入失败：{str(e)}')
        # 若有成功导入，直接跳转至资产列表
        if success_count > 0:
            return redirect('assets:asset_list')
    
    return render(request, 'assets/bulk_import.html', {
        'import_type': 'assets',
        'title': '批量导入资产',
        'categories': categories
    })


@login_required
def dashboard(request):
    """仪表板视图"""
    # 统计数据
    total_assets = Asset.objects.count()
    total_organizations = Organization.objects.count()
    total_departments = Department.objects.count()
    total_users = User.objects.count()
    
    # 资产状态统计
    asset_status_stats = Asset.objects.values('status').annotate(count=Count('status'))
    
    # 资产类别统计
    asset_category_stats = Asset.objects.values('category__name').annotate(count=Count('category'))
    
    # 最近操作日志
    recent_logs = AssetLog.objects.select_related('asset', 'operator').order_by('-created_at')[:10]
    
    context = {
        'total_assets': total_assets,
        'total_organizations': total_organizations,
        'total_departments': total_departments,
        'total_users': total_users,
        'asset_status_stats': asset_status_stats,
        'asset_category_stats': asset_category_stats,
        'recent_logs': recent_logs,
    }
    
    return render(request, 'assets/dashboard.html', context)


@login_required
def report_list(request):
    """报表列表视图"""
    return render(request, 'assets/report_list.html')


@login_required
def asset_report(request):
    """资产报表视图"""
    from django.db.models import Count, Sum, Q
    import json

    # 获取筛选参数
    category_id = request.GET.get('category')
    status = request.GET.get('status')
    department_id = request.GET.get('department')
    start_date = request.GET.get('start_date')
    end_date = request.GET.get('end_date')
    search_query = request.GET.get('search')

    # 构建查询
    assets = Asset.objects.select_related('category', 'department', 'location', 'user').all()

    if category_id:
        assets = assets.filter(category_id=category_id)

    if status:
        assets = assets.filter(status=status)

    if department_id:
        assets = assets.filter(department_id=department_id)

    if start_date:
        assets = assets.filter(purchase_date__gte=start_date)

    if end_date:
        assets = assets.filter(purchase_date__lte=end_date)

    if search_query:
        assets = assets.filter(
            Q(name__icontains=search_query) |
            Q(asset_number__icontains=search_query) |
            Q(description__icontains=search_query)
        )

    # 计算统计数据
    total_assets = assets.count()
    status_stats_query = assets.values('status').annotate(count=Count('id'))

    # 初始化状态计数 (保持旧版兼容)
    status_counts = {stat['status']: stat['count'] for stat in status_stats_query}
    in_use_count = status_counts.get('in_use', 0)
    idle_count = status_counts.get('idle', 0)
    maintenance_count = status_counts.get('maintenance', 0)
    borrowed_count = status_counts.get('borrowed', 0)
    faulty_count = status_counts.get('faulty', 0)
    scrapped_count = status_counts.get('scrapped', 0)

    # 计算总价值
    total_value = assets.aggregate(total=Sum('purchase_price'))['total'] or 0

    # 类别统计
    category_stats_query = assets.values('category__name').annotate(
        count=Count('id')
    ).order_by('-count').filter(category__name__isnull=False)[:10]

    # 部门统计
    department_stats_query = assets.values('department__name').annotate(
        count=Count('id')
    ).order_by('-count').filter(department__name__isnull=False)[:10]

    # 获取筛选选项
    categories = AssetCategory.objects.all().order_by('name')
    departments = Department.objects.all().order_by('name')
    status_choices = Asset.ASSET_STATUS_CHOICES
    status_choices_dict = dict(status_choices)

    context = {
        'assets': assets,
        'categories': categories,
        'departments': departments,
        'status_choices': status_choices,
        'selected_category': category_id,
        'selected_status': status,
        'selected_department': department_id,
        'selected_start_date': start_date,
        'selected_end_date': end_date,
        'search_query': search_query,

        # 原始统计数据，保持模板兼容
        'total_assets': total_assets,
        'in_use_count': in_use_count,
        'idle_count': idle_count,
        'maintenance_count': maintenance_count,
        'borrowed_count': borrowed_count,
        'faulty_count': faulty_count,
        'scrapped_count': scrapped_count,
        'total_value': total_value,
        'category_stats': category_stats_query,

        # 为图表准备的JSON数据
        'status_stats_json': json.dumps(list(status_stats_query)),
        'category_stats_json': json.dumps(list(category_stats_query)),
        'department_stats_json': json.dumps(list(department_stats_query)),
        'status_choices_json': json.dumps(status_choices_dict),
    }

    return render(request, 'assets/asset_report.html', context)


@login_required
def export_asset_report_excel(request):
    """导出资产报表为Excel"""
    from openpyxl import Workbook
    from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
    from openpyxl.utils import get_column_letter
    from django.db.models import Q
    from datetime import datetime
    
    # 获取筛选参数
    category_id = request.GET.get('category')
    status = request.GET.get('status')
    department_id = request.GET.get('department')
    start_date = request.GET.get('start_date')
    end_date = request.GET.get('end_date')
    search_query = request.GET.get('search')
    selected_assets = request.GET.get('selected_assets')
    
    # 构建查询
    assets = Asset.objects.select_related('category', 'department', 'location', 'user').all()
    
    # 如果有选中的资产，只导出选中的
    if selected_assets:
        asset_ids = [int(id) for id in selected_assets.split(',') if id.isdigit()]
        assets = assets.filter(id__in=asset_ids)
    else:
        # 应用筛选条件
        if category_id:
            assets = assets.filter(category_id=category_id)
        
        if status:
            assets = assets.filter(status=status)
            
        if department_id:
            assets = assets.filter(department_id=department_id)

        if start_date:
            assets = assets.filter(purchase_date__gte=start_date)

        if end_date:
            assets = assets.filter(purchase_date__lte=end_date)
        
        if search_query:
            assets = assets.filter(
                Q(name__icontains=search_query) |
                Q(asset_number__icontains=search_query) |
                Q(description__icontains=search_query)
            )
    
    # 创建工作簿
    wb = Workbook()
    ws = wb.active
    ws.title = "资产报表"
    
    # 定义样式
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center")
    border = Border(
        left=Side(style='thin'),
        right=Side(style='thin'),
        top=Side(style='thin'),
        bottom=Side(style='thin')
    )
    
    # 添加报表标题
    ws.merge_cells('A1:I1')
    title_cell = ws['A1']
    title_cell.value = f"资产报表 - {timezone.now().strftime('%Y年%m月%d日')}"
    title_cell.font = Font(bold=True, size=16)
    title_cell.alignment = Alignment(horizontal="center", vertical="center")
    ws.row_dimensions[1].height = 30
    
    # 添加筛选条件说明
    filter_info = []
    if category_id:
        try:
            category = AssetCategory.objects.get(id=category_id)
            filter_info.append(f"类别: {category.name}")
        except AssetCategory.DoesNotExist:
            pass
    if status:
        status_dict = dict(Asset.ASSET_STATUS_CHOICES)
        filter_info.append(f"状态: {status_dict.get(status, status)}")
    if department_id:
        try:
            department = Department.objects.get(id=department_id)
            filter_info.append(f"部门: {department.name}")
        except Department.DoesNotExist:
            pass

    if start_date:
        filter_info.append(f"开始日期: {start_date}")
    if end_date:
        filter_info.append(f"结束日期: {end_date}")

    if search_query:
        filter_info.append(f"搜索: {search_query}")
    if selected_assets:
        filter_info.append(f"已选择 {len(selected_assets.split(','))} 个资产")
    
    if filter_info:
        ws.merge_cells('A2:I2')
        filter_cell = ws['A2']
        filter_cell.value = f"筛选条件: {' | '.join(filter_info)}"
        filter_cell.font = Font(italic=True, color="666666")
        filter_cell.alignment = Alignment(horizontal="center")
        ws.row_dimensions[2].height = 20
        header_row = 4
    else:
        header_row = 3
    
    # 写入表头
    headers = [
        '资产名称', '资产编号', '资产类别', '所属部门', 
        '位置', '使用人', '状态', '采购日期', '采购价格'
    ]
    
    for col, header in enumerate(headers, 1):
        cell = ws.cell(row=header_row, column=col, value=header)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = header_alignment
        cell.border = border
    
    # 写入数据
    data_start_row = header_row + 1
    for row, asset in enumerate(assets, data_start_row):
        # 资产名称
        name_cell = ws.cell(row=row, column=1, value=asset.name)
        name_cell.border = border
        
        # 资产编号
        number_cell = ws.cell(row=row, column=2, value=asset.asset_number)
        number_cell.border = border
        
        # 资产类别
        category_cell = ws.cell(row=row, column=3, value=asset.category.name if asset.category else '-')
        category_cell.border = border
        
        # 所属部门
        dept_cell = ws.cell(row=row, column=4, value=asset.department.name if asset.department else '-')
        dept_cell.border = border
        
        # 位置
        location_cell = ws.cell(row=row, column=5, value=asset.location.name if asset.location else '-')
        location_cell.border = border
        
        # 使用人
        user_cell = ws.cell(row=row, column=6, value=asset.user.get_full_name() if asset.user else '-')
        user_cell.border = border
        
        # 状态
        status_dict = dict(Asset.ASSET_STATUS_CHOICES)
        status_cell = ws.cell(row=row, column=7, value=status_dict.get(asset.status, asset.status))
        status_cell.border = border
        
        # 根据状态设置颜色
        if asset.status == 'in_use':
            status_cell.fill = PatternFill(start_color="D4EDDA", end_color="D4EDDA", fill_type="solid")
        elif asset.status == 'idle':
            status_cell.fill = PatternFill(start_color="F8F9FA", end_color="F8F9FA", fill_type="solid")
        elif asset.status == 'maintenance':
            status_cell.fill = PatternFill(start_color="FFF3CD", end_color="FFF3CD", fill_type="solid")
        elif asset.status == 'scrapped':
            status_cell.fill = PatternFill(start_color="F8D7DA", end_color="F8D7DA", fill_type="solid")
        
        # 采购日期
        date_cell = ws.cell(row=row, column=8, value=asset.purchase_date.strftime('%Y-%m-%d') if asset.purchase_date else '-')
        date_cell.border = border
        
        # 采购价格
        price_cell = ws.cell(row=row, column=9, value=float(asset.purchase_price) if asset.purchase_price else 0)
        price_cell.border = border
        price_cell.number_format = '¥#,##0.00'
    
    # 添加统计行
    if assets.exists():
        stats_row = data_start_row + assets.count() + 1
        ws.merge_cells(f'A{stats_row}:H{stats_row}')
        stats_cell = ws[f'A{stats_row}']
        stats_cell.value = f"总计: {assets.count()} 个资产"
        stats_cell.font = Font(bold=True)
        stats_cell.alignment = Alignment(horizontal="right")
        
        # 总价值
        total_value = sum(float(asset.purchase_price) for asset in assets if asset.purchase_price)
        total_cell = ws.cell(row=stats_row, column=9, value=total_value)
        total_cell.font = Font(bold=True)
        total_cell.number_format = '¥#,##0.00'
        total_cell.fill = PatternFill(start_color="E2EFDA", end_color="E2EFDA", fill_type="solid")
    
    # 设置列宽
    column_widths = {
        'A': 20,  # 资产名称
        'B': 15,  # 资产编号
        'C': 12,  # 资产类别
        'D': 15,  # 所属部门
        'E': 12,  # 位置
        'F': 12,  # 使用人
        'G': 10,  # 状态
        'H': 12,  # 采购日期
        'I': 15,  # 采购价格
    }
    
    for col, width in column_widths.items():
        ws.column_dimensions[col].width = width
    
    # 冻结窗格
    ws.freeze_panes = f'A{data_start_row}'
    
    # 将工作簿保存到内存中
    buffer = BytesIO()
    wb.save(buffer)
    buffer.seek(0)
    
    # 生成文件名
    timestamp = timezone.now().strftime('%Y%m%d_%H%M%S')
    if selected_assets:
        filename = f'asset_report_selected_{timestamp}.xlsx'
    else:
        filename = f'asset_report_{timestamp}.xlsx'
    
    # 创建响应
    response = HttpResponse(
        buffer.getvalue(),
        content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    )
    response['Content-Disposition'] = f'attachment; filename={filename}'
    
    return response


@login_required
def export_asset_report_pdf(request):
    """导出资产报表为PDF（暂时禁用）"""
    messages.warning(request, 'PDF导出功能暂时不可用，请使用Excel导出功能。')
    return redirect('assets:asset_report')


@login_required
def profile_view(request):
    """个人资料查看"""
    try:
        profile = request.user.profile
    except UserProfile.DoesNotExist:
        # 如果用户没有profile，创建一个
        profile = UserProfile.objects.create(
            user=request.user,
            employee_id=f"EMP{request.user.id:06d}"
        )
    
    return render(request, 'assets/profile_view.html', {
        'profile': profile
    })


@login_required
def profile_edit(request):
    """个人资料编辑"""
    try:
        profile = request.user.profile
    except UserProfile.DoesNotExist:
        profile = UserProfile.objects.create(
            user=request.user,
            employee_id=f"EMP{request.user.id:06d}"
        )
    
    if request.method == 'POST':
        # 更新用户基本信息
        request.user.first_name = request.POST.get('first_name', '')
        request.user.last_name = request.POST.get('last_name', '')
        request.user.email = request.POST.get('email', '')
        request.user.save()
        
        # 更新用户扩展信息
        profile.employee_id = request.POST.get('employee_id', profile.employee_id)
        profile.phone = request.POST.get('phone', '')
        
        # 处理部门选择
        department_id = request.POST.get('department')
        if department_id:
            try:
                profile.department = Department.objects.get(id=department_id)
            except Department.DoesNotExist:
                profile.department = None
        else:
            profile.department = None
            
        profile.save()
        
        messages.success(request, '个人资料更新成功！')
        return redirect('assets:profile_view')
    
    departments = Department.objects.all()
    return render(request, 'assets/profile_edit.html', {
        'profile': profile,
        'departments': departments
    })


@login_required
def change_password(request):
    """修改密码"""
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password1 = request.POST.get('new_password1')
        new_password2 = request.POST.get('new_password2')
        
        # 验证原密码
        if not request.user.check_password(old_password):
            messages.error(request, '原密码不正确')
            return render(request, 'assets/change_password.html')
        
        # 验证新密码
        if new_password1 != new_password2:
            messages.error(request, '两次输入的新密码不一致')
            return render(request, 'assets/change_password.html')
        
        if len(new_password1) < 6:
            messages.error(request, '密码长度至少6位')
            return render(request, 'assets/change_password.html')
        
        # 更新密码
        request.user.set_password(new_password1)
        request.user.save()
        
        messages.success(request, '密码修改成功，请重新登录')
        return redirect('assets:login')
    
    return render(request, 'assets/change_password.html')


@login_required
def maintenance_list(request):
    """维修记录列表视图"""
    maintenances = MaintenanceRecord.objects.select_related(
        'asset', 'reporter', 'technician', 'supplier'
    ).all()
    
    # 搜索功能
    search_query = request.GET.get('search')
    if search_query:
        maintenances = maintenances.filter(
            Q(title__icontains=search_query) |
            Q(asset__name__icontains=search_query) |
            Q(asset__asset_number__icontains=search_query) |
            Q(description__icontains=search_query)
        )
    
    # 筛选功能
    status_filter = request.GET.get('status')
    if status_filter:
        maintenances = maintenances.filter(status=status_filter)
    
    priority_filter = request.GET.get('priority')
    if priority_filter:
        maintenances = maintenances.filter(priority=priority_filter)
    
    maintenance_type_filter = request.GET.get('maintenance_type')
    if maintenance_type_filter:
        maintenances = maintenances.filter(maintenance_type=maintenance_type_filter)
    
    # 分页
    paginator = Paginator(maintenances, 15)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'page_obj': page_obj,
        'search_query': search_query,
        'status_choices': MaintenanceRecord.MAINTENANCE_STATUS_CHOICES,
        'priority_choices': MaintenanceRecord.PRIORITY_CHOICES,
        'type_choices': MaintenanceRecord.MAINTENANCE_TYPE_CHOICES,
        'selected_status': status_filter,
        'selected_priority': priority_filter,
        'selected_type': maintenance_type_filter,
    }
    
    return render(request, 'assets/maintenance_list.html', context)


@login_required
def maintenance_create(request):
    """创建维修记录视图"""
    if request.method == 'POST':
        asset_id = request.POST.get('asset')
        maintenance_type = request.POST.get('maintenance_type')
        priority = request.POST.get('priority')
        title = request.POST.get('title')
        description = request.POST.get('description')
        scheduled_at = request.POST.get('scheduled_at')
        technician_id = request.POST.get('technician')
        estimated_cost = request.POST.get('estimated_cost')
        supplier_id = request.POST.get('supplier')
        
        try:
            asset = Asset.objects.get(id=asset_id)
            
            maintenance = MaintenanceRecord.objects.create(
                asset=asset,
                maintenance_type=maintenance_type,
                priority=priority,
                title=title,
                description=description,
                reporter=request.user
            )
            
            # 处理可选字段
            if scheduled_at:
                from django.utils.dateparse import parse_datetime
                maintenance.scheduled_at = parse_datetime(scheduled_at)
            
            if technician_id:
                try:
                    maintenance.technician = User.objects.get(id=technician_id)
                except User.DoesNotExist:
                    pass
            
            if estimated_cost:
                maintenance.estimated_cost = float(estimated_cost)
            
            if supplier_id:
                try:
                    maintenance.supplier = Supplier.objects.get(id=supplier_id)
                except Supplier.DoesNotExist:
                    pass
            
            maintenance.save()
            
            # 记录资产日志
            AssetLog.objects.create(
                asset=asset,
                action='maintenance',
                operator=request.user,
                description=f'创建维修记录：{title}'
            )
            
            messages.success(request, '维修记录创建成功！')
            return redirect('assets:maintenance_detail', pk=maintenance.pk)
            
        except Asset.DoesNotExist:
            messages.error(request, '指定的资产不存在')
        except Exception as e:
            messages.error(request, f'创建失败：{str(e)}')
    
    assets = Asset.objects.filter(status__in=['in_use', 'maintenance']).order_by('name')
    technicians = User.objects.filter(is_active=True).order_by('username')
    suppliers = Supplier.objects.all().order_by('name')
    
    context = {
        'assets': assets,
        'technicians': technicians,
        'suppliers': suppliers,
        'type_choices': MaintenanceRecord.MAINTENANCE_TYPE_CHOICES,
        'priority_choices': MaintenanceRecord.PRIORITY_CHOICES,
    }
    
    return render(request, 'assets/maintenance_form.html', context)


@login_required
def maintenance_detail(request, pk):
    """维修记录详情视图"""
    maintenance = get_object_or_404(MaintenanceRecord, pk=pk)
    
    context = {
        'maintenance': maintenance,
        'can_edit': request.user == maintenance.reporter or request.user.is_staff,
    }
    
    return render(request, 'assets/maintenance_detail.html', context)


@login_required
def maintenance_update(request, pk):
    """更新维修记录视图"""
    maintenance = get_object_or_404(MaintenanceRecord, pk=pk)
    
    # 权限检查
    if not (request.user == maintenance.reporter or request.user.is_staff):
        messages.error(request, '您没有权限编辑该维修记录')
        return redirect('assets:maintenance_detail', pk=pk)
    
    if request.method == 'POST':
        maintenance_type = request.POST.get('maintenance_type')
        status = request.POST.get('status')
        priority = request.POST.get('priority')
        title = request.POST.get('title')
        description = request.POST.get('description')
        solution = request.POST.get('solution')
        scheduled_at = request.POST.get('scheduled_at')
        started_at = request.POST.get('started_at')
        completed_at = request.POST.get('completed_at')
        technician_id = request.POST.get('technician')
        estimated_cost = request.POST.get('estimated_cost')
        actual_cost = request.POST.get('actual_cost')
        supplier_id = request.POST.get('supplier')
        notes = request.POST.get('notes')
        
        try:
            maintenance.maintenance_type = maintenance_type
            maintenance.status = status
            maintenance.priority = priority
            maintenance.title = title
            maintenance.description = description
            maintenance.solution = solution
            maintenance.notes = notes
            
            # 处理日期时间字段
            from django.utils.dateparse import parse_datetime
            
            if scheduled_at:
                maintenance.scheduled_at = parse_datetime(scheduled_at)
            else:
                maintenance.scheduled_at = None
                
            if started_at:
                maintenance.started_at = parse_datetime(started_at)
            else:
                maintenance.started_at = None
                
            if completed_at:
                maintenance.completed_at = parse_datetime(completed_at)
            else:
                maintenance.completed_at = None
            
            # 处理技师分配
            if technician_id:
                try:
                    maintenance.technician = User.objects.get(id=technician_id)
                except User.DoesNotExist:
                    maintenance.technician = None
            else:
                maintenance.technician = None
            
            # 处理成本信息
            if estimated_cost:
                maintenance.estimated_cost = float(estimated_cost)
            else:
                maintenance.estimated_cost = None
                
            if actual_cost:
                maintenance.actual_cost = float(actual_cost)
            else:
                maintenance.actual_cost = None
            
            # 处理供应商
            if supplier_id:
                try:
                    maintenance.supplier = Supplier.objects.get(id=supplier_id)
                except Supplier.DoesNotExist:
                    maintenance.supplier = None
            else:
                maintenance.supplier = None
            
            # 自动更新资产状态
            if status == 'in_progress' and maintenance.asset.status != 'maintenance':
                maintenance.asset.status = 'maintenance'
                maintenance.asset.save()
            elif status == 'completed' and maintenance.asset.status == 'maintenance':
                maintenance.asset.status = 'in_use'
                maintenance.asset.save()
            
            maintenance.save()
            
            # 记录资产日志
            AssetLog.objects.create(
                asset=maintenance.asset,
                action='maintenance',
                operator=request.user,
                description=f'更新维修记录：{title} - 状态：{maintenance.get_status_display()}'
            )
            
            messages.success(request, '维修记录更新成功！')
            return redirect('assets:maintenance_detail', pk=pk)
            
        except Exception as e:
            messages.error(request, f'更新失败：{str(e)}')
    
    technicians = User.objects.filter(is_active=True).order_by('username')
    suppliers = Supplier.objects.all().order_by('name')
    
    context = {
        'maintenance': maintenance,
        'technicians': technicians,
        'suppliers': suppliers,
        'type_choices': MaintenanceRecord.MAINTENANCE_TYPE_CHOICES,
        'status_choices': MaintenanceRecord.MAINTENANCE_STATUS_CHOICES,
        'priority_choices': MaintenanceRecord.PRIORITY_CHOICES,
    }
    
    return render(request, 'assets/maintenance_update.html', context)


@login_required
def asset_depreciation_report(request):
    """资产折旧报表视图"""
    assets = Asset.objects.select_related('category', 'department', 'user').all()
    
    # 筛选功能
    category_id = request.GET.get('category')
    department_id = request.GET.get('department')
    depreciation_status = request.GET.get('depreciation_status')
    
    if category_id:
        assets = assets.filter(category_id=category_id)
    
    if department_id:
        assets = assets.filter(department_id=department_id)
    
    # 按折旧状态筛选
    if depreciation_status == 'fully_depreciated':
        assets = [asset for asset in assets if asset.is_fully_depreciated]
    elif depreciation_status == 'partially_depreciated':
        assets = [asset for asset in assets if not asset.is_fully_depreciated and asset.depreciation_amount > 0]
    elif depreciation_status == 'no_depreciation':
        assets = [asset for asset in assets if asset.depreciation_amount == 0]
    
    # 计算统计信息
    total_original_value = sum(asset.purchase_price for asset in assets if asset.purchase_price)
    total_current_value = sum(asset.current_value for asset in assets)
    total_depreciation = total_original_value - total_current_value
    
    # 获取筛选选项
    categories = AssetCategory.objects.all()
    departments = Department.objects.all()
    
    context = {
        'assets': assets,
        'categories': categories,
        'departments': departments,
        'selected_category': category_id,
        'selected_department': department_id,
        'selected_depreciation_status': depreciation_status,
        'total_original_value': total_original_value,
        'total_current_value': total_current_value,
        'total_depreciation': total_depreciation,
        'depreciation_status_choices': [
            ('', '全部'),
            ('fully_depreciated', '已完全折旧'),
            ('partially_depreciated', '部分折旧'),
            ('no_depreciation', '无折旧'),
        ],
    }
    
    return render(request, 'assets/depreciation_report.html', context)


@login_required
def asset_depreciation_detail(request, pk):
    """资产折旧详情视图"""
    asset = get_object_or_404(Asset, pk=pk)
    
    # 获取折旧计划表
    depreciation_schedule = asset.calculate_depreciation_schedule()
    
    context = {
        'asset': asset,
        'depreciation_schedule': depreciation_schedule,
    }
    
    return render(request, 'assets/depreciation_detail.html', context)


@login_required
def batch_update_depreciation(request):
    """批量更新资产折旧率视图"""
    if request.method == 'POST':
        asset_ids = request.POST.getlist('asset_ids')
        new_depreciation_rate = request.POST.get('depreciation_rate')
        
        if asset_ids and new_depreciation_rate:
            try:
                depreciation_rate = float(new_depreciation_rate)
                if 0 <= depreciation_rate <= 100:
                    updated_count = Asset.objects.filter(
                        id__in=asset_ids
                    ).update(depreciation_rate=depreciation_rate)
                    
                    messages.success(request, f'成功更新 {updated_count} 个资产的折旧率为 {depreciation_rate}%')
                else:
                    messages.error(request, '折旧率必须在 0-100 之间')
            except ValueError:
                messages.error(request, '请输入有效的数字')
        else:
            messages.error(request, '请选择资产并输入折旧率')
        
        return redirect('assets:asset_depreciation_report')
    
    assets = Asset.objects.select_related('category', 'department', 'user').all()
    return render(request, 'assets/batch_depreciation_update.html', {'assets': assets})


@login_required
def inventory_plan_list(request):
    """盘点计划列表视图"""
    plans = InventoryPlan.objects.select_related('creator').prefetch_related('assignees').all()
    
    # 搜索功能
    search_query = request.GET.get('search')
    if search_query:
        plans = plans.filter(
            Q(name__icontains=search_query) |
            Q(description__icontains=search_query)
        )
    
    # 状态筛选
    status_filter = request.GET.get('status')
    if status_filter:
        plans = plans.filter(status=status_filter)
    
    # 类型筛选
    plan_type_filter = request.GET.get('plan_type')
    if plan_type_filter:
        plans = plans.filter(plan_type=plan_type_filter)
    
    # 分页
    paginator = Paginator(plans, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'page_obj': page_obj,
        'search_query': search_query,
        'status_choices': InventoryPlan.PLAN_STATUS_CHOICES,
        'type_choices': InventoryPlan.PLAN_TYPE_CHOICES,
        'selected_status': status_filter,
        'selected_type': plan_type_filter,
    }
    
    return render(request, 'assets/inventory_plan_list.html', context)


@login_required
def inventory_plan_create(request):
    """创建盘点计划视图"""
    if request.method == 'POST':
        name = request.POST.get('name')
        plan_type = request.POST.get('plan_type')
        description = request.POST.get('description')
        planned_start_date = request.POST.get('planned_start_date')
        planned_end_date = request.POST.get('planned_end_date')
        
        # 获取选中的范围
        department_ids = request.POST.getlist('departments')
        category_ids = request.POST.getlist('categories')
        location_ids = request.POST.getlist('locations')
        assignee_ids = request.POST.getlist('assignees')
        
        try:
            from django.utils.dateparse import parse_datetime
            
            # 创建盘点计划
            plan = InventoryPlan.objects.create(
                name=name,
                plan_type=plan_type,
                description=description,
                planned_start_date=parse_datetime(planned_start_date),
                planned_end_date=parse_datetime(planned_end_date),
                creator=request.user
            )
            
            # 设置盘点范围
            if department_ids:
                plan.departments.set(department_ids)
            if category_ids:
                plan.categories.set(category_ids)
            if location_ids:
                plan.locations.set(location_ids)
            if assignee_ids:
                plan.assignees.set(assignee_ids)
            
            # 根据范围生成盘点项目
            assets_query = Asset.objects.all()
            
            if department_ids:
                assets_query = assets_query.filter(department_id__in=department_ids)
            if category_ids:
                assets_query = assets_query.filter(category_id__in=category_ids)
            if location_ids:
                assets_query = assets_query.filter(location_id__in=location_ids)
            
            # 抽查盘点的特殊处理
            if plan_type == 'spot_check':
                import random
                assets_list = list(assets_query)
                # 抽取随机20%的资产
                sample_size = max(1, len(assets_list) // 5)
                assets_query = random.sample(assets_list, min(sample_size, len(assets_list)))
            
            # 创建盘点项目
            for asset in assets_query:
                InventoryItem.objects.create(
                    plan=plan,
                    asset=asset,
                    system_status=asset.status,
                    system_location=asset.location,
                    system_user=asset.user,
                    system_department=asset.department
                )
            
            messages.success(request, f'盘点计划“{name}”创建成功，共包含 {plan.total_assets_count} 个资产。')
            return redirect('assets:inventory_plan_detail', pk=plan.pk)
            
        except Exception as e:
            messages.error(request, f'创建失败：{str(e)}')
    
    # 获取选项数据
    departments = Department.objects.all().order_by('name')
    categories = AssetCategory.objects.all().order_by('name')
    locations = Location.objects.all().order_by('name')
    users = User.objects.filter(is_active=True).order_by('username')
    
    context = {
        'departments': departments,
        'categories': categories,
        'locations': locations,
        'users': users,
        'type_choices': InventoryPlan.PLAN_TYPE_CHOICES,
    }
    
    return render(request, 'assets/inventory_plan_form.html', context)


@login_required
def inventory_plan_detail(request, pk):
    """盘点计划详情视图"""
    plan = get_object_or_404(InventoryPlan, pk=pk)
    
    # 获取盘点项目统计
    items = plan.inventory_items.select_related('asset', 'assigned_to', 'checked_by')
    
    # 筛选功能
    status_filter = request.GET.get('status')
    if status_filter:
        items = items.filter(status=status_filter)
    
    discrepancy_filter = request.GET.get('discrepancy')
    if discrepancy_filter == 'yes':
        items = items.exclude(discrepancy_type='none')
    elif discrepancy_filter == 'no':
        items = items.filter(discrepancy_type='none')
    
    # 分页
    paginator = Paginator(items, 20)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # 统计信息
    stats = {
        'total': plan.total_assets_count,
        'completed': plan.completed_assets_count,
        'pending': items.filter(status='pending').count(),
        'in_progress': items.filter(status='in_progress').count(),
        'discrepancies': plan.discrepancy_count,
        'progress': plan.progress_percentage,
    }
    
    context = {
        'plan': plan,
        'page_obj': page_obj,
        'stats': stats,
        'status_choices': InventoryItem.ITEM_STATUS_CHOICES,
        'selected_status': status_filter,
        'selected_discrepancy': discrepancy_filter,
        'can_edit': request.user == plan.creator or request.user.is_staff,
    }
    
    return render(request, 'assets/inventory_plan_detail.html', context)


@login_required
def inventory_plan_start(request, pk):
    """启动盘点计划"""
    plan = get_object_or_404(InventoryPlan, pk=pk)
    
    if plan.status != 'draft':
        messages.error(request, '只有草稿状态的计划才能启动')
        return redirect('assets:inventory_plan_detail', pk=pk)
    
    if request.method == 'POST':
        from django.utils import timezone
        
        plan.status = 'active'
        plan.actual_start_date = timezone.now()
        plan.save()
        
        messages.success(request, f'盘点计划“{plan.name}”已启动')
        return redirect('assets:inventory_plan_detail', pk=pk)
    
    return render(request, 'assets/inventory_plan_start_confirm.html', {'plan': plan})


@login_required
def inventory_item_check(request, pk):
    """盘点项目检查视图"""
    item = get_object_or_404(InventoryItem, pk=pk)
    
    if item.plan.status != 'active':
        messages.error(request, '只有激活状态的计划才能进行盘点')
        return redirect('assets:inventory_plan_detail', pk=item.plan.pk)
    
    if request.method == 'POST':
        from django.utils import timezone
        
        # 更新盘点信息
        item.status = request.POST.get('status', 'completed')
        item.physical_status = request.POST.get('physical_status')
        item.discrepancy_notes = request.POST.get('discrepancy_notes', '')
        
        # 处理位置信息
        physical_location_id = request.POST.get('physical_location')
        if physical_location_id:
            try:
                item.physical_location = Location.objects.get(id=physical_location_id)
            except Location.DoesNotExist:
                pass
        
        # 处理使用人信息
        physical_user_id = request.POST.get('physical_user')
        if physical_user_id:
            try:
                item.physical_user = User.objects.get(id=physical_user_id)
            except User.DoesNotExist:
                pass
        
        # 处理部门信息
        physical_department_id = request.POST.get('physical_department')
        if physical_department_id:
            try:
                item.physical_department = Department.objects.get(id=physical_department_id)
            except Department.DoesNotExist:
                pass
        
        # 设置盘点人员和时间
        item.checked_by = request.user
        item.checked_at = timezone.now()
        
        # 自动检测差异
        discrepancies = item.detect_discrepancies()
        
        # 手动设置差异类型
        manual_discrepancy = request.POST.get('discrepancy_type')
        if manual_discrepancy and manual_discrepancy != 'none':
            item.discrepancy_type = manual_discrepancy
        
        item.save()
        
        # 记录资产日志
        AssetLog.objects.create(
            asset=item.asset,
            action='update',
            operator=request.user,
            description=f'资产盘点：{item.plan.name} - 状态：{item.get_status_display()}'
        )
        
        if item.has_discrepancies():
            messages.warning(request, f'盘点完成，发现差异：{item.get_discrepancy_type_display()}')
        else:
            messages.success(request, '盘点完成，无差异')
        
        return redirect('assets:inventory_plan_detail', pk=item.plan.pk)
    
    # 获取选项数据
    locations = Location.objects.all().order_by('name')
    users = User.objects.filter(is_active=True).order_by('username')
    departments = Department.objects.all().order_by('name')
    
    context = {
        'item': item,
        'locations': locations,
        'users': users,
        'departments': departments,
        'status_choices': InventoryItem.ITEM_STATUS_CHOICES,
        'discrepancy_choices': InventoryItem.DISCREPANCY_TYPE_CHOICES,
        'asset_status_choices': Asset.ASSET_STATUS_CHOICES,
    }
    
    return render(request, 'assets/inventory_item_check.html', context)


@login_required
def borrow_request_list(request):
    """借用申请列表视图"""
    borrows = AssetBorrow.objects.select_related('asset', 'borrower', 'approver', 'borrower_department').all()
    
    # 搜索功能
    search_query = request.GET.get('search')
    if search_query:
        borrows = borrows.filter(
            Q(asset__name__icontains=search_query) |
            Q(asset__asset_number__icontains=search_query) |
            Q(borrower__username__icontains=search_query) |
            Q(borrower__first_name__icontains=search_query) |
            Q(borrower__last_name__icontains=search_query)
        )
    
    # 状态筛选
    status_filter = request.GET.get('status')
    if status_filter:
        borrows = borrows.filter(status=status_filter)
    
    # 部门筛选
    department_filter = request.GET.get('department')
    if department_filter:
        borrows = borrows.filter(borrower_department_id=department_filter)
    
    # 逾期筛选
    overdue_filter = request.GET.get('overdue')
    if overdue_filter == 'yes':
        from django.utils import timezone
        borrows = borrows.filter(
            status='borrowed',
            expected_return_date__lt=timezone.now()
        )
    
    # 排序
    borrows = borrows.order_by('-created_at')
    
    # 分页
    paginator = Paginator(borrows, 15)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # 获取选项数据
    departments = Department.objects.all().order_by('name')
    
    context = {
        'page_obj': page_obj,
        'search_query': search_query,
        'status_choices': AssetBorrow.BORROW_STATUS_CHOICES,
        'departments': departments,
        'selected_status': status_filter,
        'selected_department': department_filter,
        'selected_overdue': overdue_filter,
    }
    
    return render(request, 'assets/borrow_request_list.html', context)


@login_required
def borrow_request_create(request):
    """创建借用申请视图"""
    if request.method == 'POST':
        asset_id = request.POST.get('asset')
        purpose = request.POST.get('purpose')
        purpose_description = request.POST.get('purpose_description')
        expected_return_date = request.POST.get('expected_return_date')
        contact_phone = request.POST.get('contact_phone', '')
        contact_email = request.POST.get('contact_email', '')
        
        try:
            from django.utils.dateparse import parse_datetime
            
            asset = Asset.objects.get(id=asset_id)
            
            # 检查资产是否可借用
            if asset.status not in ['idle', 'in_use']:
                messages.error(request, f'资产“{asset.name}”当前状态为“{asset.get_status_display()}”，无法借用')
                return redirect('assets:borrow_request_create')
            
            # 检查是否已有未完成的借用申请
            existing_borrow = AssetBorrow.objects.filter(
                asset=asset,
                status__in=['pending', 'approved', 'borrowed']
            ).first()
            
            if existing_borrow:
                messages.error(request, f'资产“{asset.name}”已有未完成的借用申请')
                return redirect('assets:borrow_request_create')
            
            # 创建借用申请
            borrow = AssetBorrow.objects.create(
                asset=asset,
                borrower=request.user,
                borrower_department=request.user.userprofile.department if hasattr(request.user, 'userprofile') else None,
                purpose=purpose,
                purpose_description=purpose_description,
                expected_return_date=parse_datetime(expected_return_date),
                contact_phone=contact_phone,
                contact_email=contact_email
            )
            
            # 记录资产日志
            AssetLog.objects.create(
                asset=asset,
                action='update',
                operator=request.user,
                description=f'提交借用申请：{purpose_description[:50]}'
            )
            
            messages.success(request, f'借用申请提交成功，等待审批')
            return redirect('assets:borrow_request_detail', pk=borrow.pk)
            
        except Asset.DoesNotExist:
            messages.error(request, '资产不存在')
        except Exception as e:
            messages.error(request, f'创建失败：{str(e)}')
    
    # 获取可借用的资产
    available_assets = Asset.objects.filter(
        status__in=['idle', 'in_use']
    ).exclude(
        borrow_records__status__in=['pending', 'approved', 'borrowed']
    ).select_related('category', 'department').order_by('name')
    
    context = {
        'assets': available_assets,
        'purpose_choices': AssetBorrow.BORROW_PURPOSE_CHOICES,
    }
    
    return render(request, 'assets/borrow_request_form.html', context)


@login_required
def borrow_request_detail(request, pk):
    """借用申请详情视图"""
    borrow = get_object_or_404(AssetBorrow, pk=pk)
    
    context = {
        'borrow': borrow,
        'can_approve': request.user.is_staff and borrow.can_approve,
        'can_borrow_out': request.user.is_staff and borrow.can_borrow,
        'can_return': request.user.is_staff and borrow.can_return,
        'can_cancel': (request.user == borrow.borrower or request.user.is_staff) and borrow.can_cancel,
        'is_borrower': request.user == borrow.borrower,
    }
    
    return render(request, 'assets/borrow_request_detail.html', context)


@login_required
def borrow_request_approve(request, pk):
    """审批借用申请视图"""
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:borrow_request_list')
    
    borrow = get_object_or_404(AssetBorrow, pk=pk)
    
    if not borrow.can_approve:
        messages.error(request, '该申请已经审批或无法审批')
        return redirect('assets:borrow_request_detail', pk=pk)
    
    if request.method == 'POST':
        action = request.POST.get('action')
        notes = request.POST.get('notes', '')
        
        if action == 'approve':
            if borrow.approve(request.user, notes):
                AssetLog.objects.create(
                    asset=borrow.asset,
                    action='update',
                    operator=request.user,
                    description=f'借用申请审批通过：{notes[:50] if notes else "无备注"}'
                )
                messages.success(request, '审批通过')
            else:
                messages.error(request, '审批失败')
        elif action == 'reject':
            if borrow.reject(request.user, notes):
                AssetLog.objects.create(
                    asset=borrow.asset,
                    action='update',
                    operator=request.user,
                    description=f'借用申请审批拒绝：{notes[:50] if notes else "无备注"}'
                )
                messages.success(request, '审批拒绝')
            else:
                messages.error(request, '审批失败')
        
        return redirect('assets:borrow_request_detail', pk=pk)
    
    context = {
        'borrow': borrow,
    }
    
    return render(request, 'assets/borrow_request_approve.html', context)


@login_required
def borrow_asset_out(request, pk):
    """办理资产借出视图"""
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:borrow_request_list')
    
    borrow = get_object_or_404(AssetBorrow, pk=pk)
    
    if not borrow.can_borrow:
        messages.error(request, '该申请尚未审批或无法借出')
        return redirect('assets:borrow_request_detail', pk=pk)
    
    if request.method == 'POST':
        if borrow.borrow_out(request.user):
            AssetLog.objects.create(
                asset=borrow.asset,
                action='update',
                operator=request.user,
                description=f'资产借出给：{borrow.borrower.get_full_name() or borrow.borrower.username}'
            )
            messages.success(request, f'资产“{borrow.asset.name}”已成功借出')
        else:
            messages.error(request, '借出失败')
        
        return redirect('assets:borrow_request_detail', pk=pk)
    
    context = {
        'borrow': borrow,
    }
    
    return render(request, 'assets/borrow_asset_out.html', context)


@login_required
def return_asset(request, pk):
    """办理资产归还视图"""
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:borrow_request_list')
    
    borrow = get_object_or_404(AssetBorrow, pk=pk)
    
    if not borrow.can_return:
        messages.error(request, '该资产尚未借出或无法归还')
        return redirect('assets:borrow_request_detail', pk=pk)
    
    if request.method == 'POST':
        condition = request.POST.get('condition', '')
        notes = request.POST.get('notes', '')
        
        if borrow.return_asset(request.user, condition, notes):
            AssetLog.objects.create(
                asset=borrow.asset,
                action='update',
                operator=request.user,
                description=f'资产归还：{condition[:50] if condition else "正常归还"}'
            )
            messages.success(request, f'资产“{borrow.asset.name}”已成功归还')
        else:
            messages.error(request, '归还失败')
        
        return redirect('assets:borrow_request_detail', pk=pk)
    
    context = {
        'borrow': borrow,
        'asset_status_choices': Asset.ASSET_STATUS_CHOICES,
    }
    
    return render(request, 'assets/return_asset.html', context)


@login_required
def my_borrow_requests(request):
    """我的借用申请列表"""
    borrows = AssetBorrow.objects.filter(
        borrower=request.user
    ).select_related('asset', 'approver').order_by('-created_at')
    
    # 状态筛选
    status_filter = request.GET.get('status')
    if status_filter:
        borrows = borrows.filter(status=status_filter)
    
    # 分页
    paginator = Paginator(borrows, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'page_obj': page_obj,
        'status_choices': AssetBorrow.BORROW_STATUS_CHOICES,
        'selected_status': status_filter,
    }
    
    return render(request, 'assets/my_borrow_requests.html', context)


@login_required
def overdue_borrows(request):
    """逾期借用列表视图"""
    if not request.user.is_staff:
        messages.error(request, '您没有权限访问此页面')
        return redirect('assets:borrow_request_list')
    
    from django.utils import timezone
    
    # 更新逾期状态
    overdue_borrows = AssetBorrow.objects.filter(
        status='borrowed',
        expected_return_date__lt=timezone.now()
    )
    
    for borrow in overdue_borrows:
        borrow.update_overdue_status()
    
    # 获取逾期列表
    borrows = AssetBorrow.objects.filter(
        status='overdue'
    ).select_related('asset', 'borrower', 'borrower_department').order_by('expected_return_date')
    
    # 分页
    paginator = Paginator(borrows, 15)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'page_obj': page_obj,
    }
    
    return render(request, 'assets/overdue_borrows.html', context)


# ==================== 库存预警管理视图 ====================

@login_required
def inventory_alert_dashboard(request):
    """库存预警仪表板"""
    from .models import InventoryAlert
    
    # 获取各类预警统计
    active_alerts = InventoryAlert.objects.filter(status='active')
    urgent_alerts = active_alerts.filter(priority='urgent')
    out_of_stock_alerts = active_alerts.filter(alert_type='out_of_stock')
    low_stock_alerts = active_alerts.filter(alert_type='low_stock')
    reorder_alerts = active_alerts.filter(alert_type='reorder_point')
    
    # 获取需要关注的耗材
    from decimal import Decimal
    critical_consumables = Consumable.objects.filter(
        quantity__lte=F('min_quantity') * Decimal('0.5')
    ).select_related('category', 'warehouse', 'supplier')[:10]
    
    # 获取最新的预警
    recent_alerts = active_alerts.select_related('consumable')[:10]
    
    # 生成新的预警
    if request.user.is_staff:
        new_low_stock = InventoryAlert.generate_low_stock_alerts()
        new_out_of_stock = InventoryAlert.generate_out_of_stock_alerts()
        new_reorder = InventoryAlert.generate_reorder_alerts()
        
        if new_low_stock + new_out_of_stock + new_reorder > 0:
            messages.info(request, f'已生成 {new_low_stock + new_out_of_stock + new_reorder} 个新预警')
    
    context = {
        'total_active_alerts': active_alerts.count(),
        'urgent_alerts_count': urgent_alerts.count(),
        'out_of_stock_count': out_of_stock_alerts.count(),
        'low_stock_count': low_stock_alerts.count(),
        'reorder_count': reorder_alerts.count(),
        'critical_consumables': critical_consumables,
        'recent_alerts': recent_alerts,
    }
    
    return render(request, 'assets/inventory_alert_dashboard.html', context)


@login_required
def inventory_alert_list(request):
    """库存预警列表"""
    from .models import InventoryAlert
    
    alerts = InventoryAlert.objects.select_related(
        'consumable', 'consumable__category', 'consumable__warehouse',
        'acknowledged_by', 'resolved_by'
    ).all()
    
    # 搜索功能
    search_query = request.GET.get('search')
    if search_query:
        alerts = alerts.filter(
            Q(consumable__name__icontains=search_query) |
            Q(consumable__code__icontains=search_query) |
            Q(message__icontains=search_query)
        )
    
    # 筛选功能
    alert_type_filter = request.GET.get('alert_type')
    if alert_type_filter:
        alerts = alerts.filter(alert_type=alert_type_filter)
    
    status_filter = request.GET.get('status')
    if status_filter:
        alerts = alerts.filter(status=status_filter)
    
    priority_filter = request.GET.get('priority')
    if priority_filter:
        alerts = alerts.filter(priority=priority_filter)
    
    warehouse_filter = request.GET.get('warehouse')
    if warehouse_filter:
        alerts = alerts.filter(consumable__warehouse_id=warehouse_filter)
    
    # 排序
    alerts = alerts.order_by('-created_at')
    
    # 分页
    paginator = Paginator(alerts, 20)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # 获取筛选选项
    warehouses = Warehouse.objects.all().order_by('name')
    
    context = {
        'page_obj': page_obj,
        'search_query': search_query,
        'alert_type_choices': InventoryAlert.ALERT_TYPE_CHOICES,
        'status_choices': InventoryAlert.ALERT_STATUS_CHOICES,
        'priority_choices': InventoryAlert.PRIORITY_CHOICES,
        'warehouses': warehouses,
        'selected_alert_type': alert_type_filter,
        'selected_status': status_filter,
        'selected_priority': priority_filter,
        'selected_warehouse': warehouse_filter,
    }
    
    return render(request, 'assets/inventory_alert_list.html', context)


@login_required
def inventory_alert_detail(request, pk):
    """库存预警详情"""
    from .models import InventoryAlert
    
    alert = get_object_or_404(InventoryAlert, pk=pk)
    
    # 获取相关的库存历史记录
    recent_logs = InventoryLog.objects.filter(
        consumable=alert.consumable
    ).select_related('operator').order_by('-created_at')[:10]
    
    context = {
        'alert': alert,
        'recent_logs': recent_logs,
        'can_manage': request.user.is_staff,
    }
    
    return render(request, 'assets/inventory_alert_detail.html', context)


@login_required
def inventory_alert_acknowledge(request, pk):
    """确认库存预警"""
    from .models import InventoryAlert
    
    alert = get_object_or_404(InventoryAlert, pk=pk)
    
    if request.method == 'POST':
        if alert.acknowledge(request.user):
            messages.success(request, f'预警 "{alert.consumable.name}" 已确认')
        else:
            messages.error(request, '确认失败')
        
        return redirect('assets:inventory_alert_detail', pk=pk)
    
    return redirect('assets:inventory_alert_detail', pk=pk)


@login_required
def inventory_alert_resolve(request, pk):
    """解决库存预警"""
    from .models import InventoryAlert
    
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:inventory_alert_list')
    
    alert = get_object_or_404(InventoryAlert, pk=pk)
    
    if request.method == 'POST':
        if alert.resolve(request.user):
            messages.success(request, f'预警 "{alert.consumable.name}" 已解决')
        else:
            messages.error(request, '解决失败')
        
        return redirect('assets:inventory_alert_detail', pk=pk)
    
    return redirect('assets:inventory_alert_detail', pk=pk)


@login_required
def inventory_alert_ignore(request, pk):
    """忽略库存预警"""
    from .models import InventoryAlert
    
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:inventory_alert_list')
    
    alert = get_object_or_404(InventoryAlert, pk=pk)
    
    if request.method == 'POST':
        if alert.ignore():
            messages.success(request, f'预警 "{alert.consumable.name}" 已忽略')
        else:
            messages.error(request, '忽略失败')
        
        return redirect('assets:inventory_alert_detail', pk=pk)
    
    return redirect('assets:inventory_alert_detail', pk=pk)


@login_required
def inventory_alert_batch_action(request):
    """批量操作库存预警"""
    from .models import InventoryAlert
    
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:inventory_alert_list')
    
    if request.method == 'POST':
        alert_ids = request.POST.getlist('alert_ids')
        action = request.POST.get('action')
        
        if not alert_ids:
            messages.error(request, '请选择要操作的预警')
            return redirect('assets:inventory_alert_list')
        
        alerts = InventoryAlert.objects.filter(id__in=alert_ids)
        success_count = 0
        
        if action == 'acknowledge':
            for alert in alerts:
                if alert.acknowledge(request.user):
                    success_count += 1
            messages.success(request, f'已确认 {success_count} 个预警')
            
        elif action == 'resolve':
            for alert in alerts:
                if alert.resolve(request.user):
                    success_count += 1
            messages.success(request, f'已解决 {success_count} 个预警')
            
        elif action == 'ignore':
            for alert in alerts:
                if alert.ignore():
                    success_count += 1
            messages.success(request, f'已忽略 {success_count} 个预警')
        
        else:
            messages.error(request, '无效的操作')
    
    return redirect('assets:inventory_alert_list')


@login_required
def generate_inventory_alerts(request):
    """手动生成库存预警"""
    from .models import InventoryAlert
    
    if not request.user.is_staff:
        messages.error(request, '您没有权限进行此操作')
        return redirect('assets:inventory_alert_list')
    
    if request.method == 'POST':
        try:
            new_low_stock = InventoryAlert.generate_low_stock_alerts()
            new_out_of_stock = InventoryAlert.generate_out_of_stock_alerts()
            new_reorder = InventoryAlert.generate_reorder_alerts()
            
            total_new = new_low_stock + new_out_of_stock + new_reorder
            
            if total_new > 0:
                messages.success(
                    request, 
                    f'成功生成 {total_new} 个新预警：'
                    f'低库存 {new_low_stock} 个，'
                    f'缺货 {new_out_of_stock} 个，'
                    f'补货提醒 {new_reorder} 个'
                )
            else:
                messages.info(request, '当前没有需要生成的新预警')
                
        except Exception as e:
            messages.error(request, f'生成预警失败：{str(e)}')
    
    return redirect('assets:inventory_alert_list')


@login_required
def inventory_status_overview(request):
    """库存状态总览"""
    # 获取所有耗材的库存状态
    consumables = Consumable.objects.select_related(
        'category', 'warehouse', 'supplier'
    ).all()
    
    # 按状态分类
    normal_stock = []
    reorder_needed = []
    low_stock = []
    out_of_stock = []
    
    for consumable in consumables:
        level = consumable.get_current_alert_level()
        if level == 'normal':
            normal_stock.append(consumable)
        elif level == 'reorder_point':
            reorder_needed.append(consumable)
        elif level == 'low_stock':
            low_stock.append(consumable)
        elif level == 'out_of_stock':
            out_of_stock.append(consumable)
    
    # 筛选功能
    status_filter = request.GET.get('status', 'all')
    warehouse_filter = request.GET.get('warehouse')
    category_filter = request.GET.get('category')
    
    if status_filter == 'normal':
        filtered_consumables = normal_stock
    elif status_filter == 'reorder':
        filtered_consumables = reorder_needed
    elif status_filter == 'low_stock':
        filtered_consumables = low_stock
    elif status_filter == 'out_of_stock':
        filtered_consumables = out_of_stock
    else:
        filtered_consumables = list(consumables)
    
    # 应用仓库和类别筛选
    if warehouse_filter:
        filtered_consumables = [c for c in filtered_consumables if str(c.warehouse_id) == warehouse_filter]
    
    if category_filter:
        filtered_consumables = [c for c in filtered_consumables if str(c.category_id) == category_filter]
    
    # 分页
    paginator = Paginator(filtered_consumables, 20)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # 获取筛选选项
    warehouses = Warehouse.objects.all().order_by('name')
    categories = AssetCategory.objects.all().order_by('name')
    
    context = {
        'page_obj': page_obj,
        'normal_count': len(normal_stock),
        'reorder_count': len(reorder_needed),
        'low_stock_count': len(low_stock),
        'out_of_stock_count': len(out_of_stock),
        'total_count': consumables.count(),
        'warehouses': warehouses,
        'categories': categories,
        'selected_status': status_filter,
        'selected_warehouse': warehouse_filter,
        'selected_category': category_filter,
    }
    
    return render(request, 'assets/inventory_status_overview.html', context)


# ==================== 报表与分析视图 ====================

@login_required
def analytics_dashboard(request):
    """分析仪表板"""
    from django.db.models import Count, Sum, Avg, Q, F
    from django.utils import timezone
    from datetime import datetime, timedelta
    import json
    
    # 获取时间范围
    end_date = timezone.now()
    start_date = end_date - timedelta(days=30)
    
    # 资产统计
    total_assets = Asset.objects.count()
    assets_by_status = Asset.objects.values('status').annotate(count=Count('id'))
    assets_by_category = Asset.objects.values('category__name').annotate(count=Count('id'))[:10]
    assets_by_department = Asset.objects.values('department__name').annotate(count=Count('id'))[:10]
    
    # 维修统计
    maintenance_stats = MaintenanceRecord.objects.filter(
        created_at__gte=start_date
    ).values('status').annotate(count=Count('id'))
    
    # 借用统计
    borrow_stats = AssetBorrow.objects.filter(
        created_at__gte=start_date
    ).values('status').annotate(count=Count('id'))
    
    # 库存预警统计
    from .models import InventoryAlert
    alert_stats = InventoryAlert.objects.filter(
        created_at__gte=start_date
    ).values('alert_type').annotate(count=Count('id'))
    
    # 耗材统计
    total_consumables = Consumable.objects.count()
    low_stock_consumables = Consumable.objects.filter(
        quantity__lte=F('min_quantity')
    ).count()
    
    # 资产价值统计
    asset_value_stats = Asset.objects.aggregate(
        total_value=Sum('purchase_price'),
        avg_value=Avg('purchase_price')
    )
    
    # 准备图表数据
    chart_data = {
        'assets_by_status': list(assets_by_status),
        'assets_by_category': list(assets_by_category),
        'assets_by_department': list(assets_by_department),
        'maintenance_stats': list(maintenance_stats),
        'borrow_stats': list(borrow_stats),
        'alert_stats': list(alert_stats),
    }
    
    context = {
        'total_assets': total_assets,
        'total_consumables': total_consumables,
        'low_stock_consumables': low_stock_consumables,
        'asset_value_stats': asset_value_stats,
        'chart_data': json.dumps(chart_data),
        'date_range': {
            'start': start_date.strftime('%Y-%m-%d'),
            'end': end_date.strftime('%Y-%m-%d')
        }
    }
    
    return render(request, 'assets/analytics_dashboard.html', context)


@login_required
def asset_analytics(request):
    """资产分析报表"""
    from django.db.models import Count, Sum, Avg, Q
    from django.utils import timezone
    from datetime import datetime, timedelta
    from decimal import Decimal
    import json
    
    class DecimalEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, Decimal):
                return float(obj)
            return super(DecimalEncoder, self).default(obj)
    
    # 获取筛选参数
    date_range = request.GET.get('date_range', '30')
    category_filter = request.GET.get('category')
    department_filter = request.GET.get('department')
    
    # 计算时间范围
    end_date = timezone.now()
    if date_range == '7':
        start_date = end_date - timedelta(days=7)
    elif date_range == '90':
        start_date = end_date - timedelta(days=90)
    elif date_range == '365':
        start_date = end_date - timedelta(days=365)
    else:
        start_date = end_date - timedelta(days=30)
    
    # 基础查询
    assets = Asset.objects.all()
    
    if category_filter:
        assets = assets.filter(category_id=category_filter)
    if department_filter:
        assets = assets.filter(department_id=department_filter)
    
    # 资产统计
    total_assets = assets.count()
    total_value = assets.aggregate(Sum('purchase_price'))['purchase_price__sum'] or 0
    avg_value = assets.aggregate(Avg('purchase_price'))['purchase_price__avg'] or 0
    
    # 按状态分组
    status_stats = assets.values('status').annotate(count=Count('id'))
    
    # 按类别分组
    category_stats = assets.values('category__name').annotate(
        count=Count('id'),
        total_value=Sum('purchase_price')
    ).order_by('-count')
    
    # 按部门分组
    department_stats = assets.values('department__name').annotate(
        count=Count('id'),
        total_value=Sum('purchase_price')
    ).order_by('-count')
    
    # 按采购年份分组 - 数据库兼容方式
    yearly_purchases = assets.extra(
        select={'year': get_date_format_sql('purchase_date', 'year')}
    ).values('year').annotate(
        count=Count('id'),
        total_value=Sum('purchase_price')
    ).order_by('year')
    
    # 准备图表数据
    chart_data = {
        'status_stats': list(status_stats),
        'category_stats': list(category_stats),
        'department_stats': list(department_stats),
        'yearly_purchases': list(yearly_purchases),
    }
    
    # 获取筛选选项
    categories = AssetCategory.objects.all().order_by('name')
    departments = Department.objects.all().order_by('name')
    
    context = {
        'total_assets': total_assets,
        'total_value': total_value,
        'avg_value': avg_value,
        'status_stats': status_stats,
        'category_stats': category_stats,
        'department_stats': department_stats,
        'yearly_purchases': yearly_purchases,
        'chart_data': json.dumps(chart_data, cls=DecimalEncoder),
        'categories': categories,
        'departments': departments,
        'selected_date_range': date_range,
        'selected_category': category_filter,
        'selected_department': department_filter,
        'date_range_options': [
            ('7', '最近7天'),
            ('30', '最近30天'),
            ('90', '最近90天'),
            ('365', '最近一年'),
        ]
    }
    
    return render(request, 'assets/asset_analytics.html', context)


@login_required
def maintenance_analytics(request):
    """维修分析报表"""
    from django.db.models import Count, Sum, Avg, Q
    from decimal import Decimal
    import json
    
    class DecimalEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, Decimal):
                return float(obj)
            return super(DecimalEncoder, self).default(obj)
    from django.utils import timezone
    from datetime import datetime, timedelta
    import json
    
    # 获取筛选参数
    date_range = request.GET.get('date_range', '30')
    maintenance_type = request.GET.get('maintenance_type')
    priority = request.GET.get('priority')
    
    # 计算时间范围
    end_date = timezone.now()
    if date_range == '7':
        start_date = end_date - timedelta(days=7)
    elif date_range == '90':
        start_date = end_date - timedelta(days=90)
    elif date_range == '365':
        start_date = end_date - timedelta(days=365)
    else:
        start_date = end_date - timedelta(days=30)
    
    # 基础查询
    maintenance_records = MaintenanceRecord.objects.filter(
        created_at__gte=start_date
    )
    
    if maintenance_type:
        maintenance_records = maintenance_records.filter(maintenance_type=maintenance_type)
    if priority:
        maintenance_records = maintenance_records.filter(priority=priority)
    
    # 基础统计
    total_records = maintenance_records.count()
    completed_records = maintenance_records.filter(status='completed').count()
    pending_records = maintenance_records.filter(status='pending').count()
    in_progress_records = maintenance_records.filter(status='in_progress').count()
    
    # 成本统计
    cost_stats = maintenance_records.aggregate(
        total_estimated_cost=Sum('estimated_cost'),
        total_actual_cost=Sum('actual_cost'),
        avg_estimated_cost=Avg('estimated_cost'),
        avg_actual_cost=Avg('actual_cost')
    )
    
    # 按状态分组
    status_stats = maintenance_records.values('status').annotate(count=Count('id'))
    
    # 按类型分组
    type_stats = maintenance_records.values('maintenance_type').annotate(
        count=Count('id'),
        total_cost=Sum('actual_cost')
    )
    
    # 按优先级分组
    priority_stats = maintenance_records.values('priority').annotate(count=Count('id'))
    
    # 按月份分组 - 数据库兼容方式
    monthly_stats = maintenance_records.extra(
        select={'month': get_date_format_sql('created_at', 'month')}
    ).values('month').annotate(
        count=Count('id'),
        total_cost=Sum('actual_cost')
    ).order_by('month')
    
    # 准备图表数据
    chart_data = {
        'status_stats': list(status_stats),
        'type_stats': list(type_stats),
        'priority_stats': list(priority_stats),
        'monthly_stats': list(monthly_stats),
    }
    
    context = {
        'total_records': total_records,
        'completed_records': completed_records,
        'pending_records': pending_records,
        'in_progress_records': in_progress_records,
        'cost_stats': cost_stats,
        'status_stats': status_stats,
        'type_stats': type_stats,
        'priority_stats': priority_stats,
        'monthly_stats': monthly_stats,
        'chart_data': json.dumps(chart_data, cls=DecimalEncoder),
        'selected_date_range': date_range,
        'selected_maintenance_type': maintenance_type,
        'selected_priority': priority,
        'date_range_options': [
            ('7', '最近7天'),
            ('30', '最近30天'),
            ('90', '最近90天'),
            ('365', '最近一年'),
        ],
        'maintenance_type_choices': MaintenanceRecord.MAINTENANCE_TYPE_CHOICES,
        'priority_choices': MaintenanceRecord.PRIORITY_CHOICES,
    }
    
    return render(request, 'assets/maintenance_analytics.html', context)


@login_required
def inventory_analytics(request):
    """库存分析报表"""
    from django.db.models import Count, Sum, Avg, Q, F
    from django.db import models
    from decimal import Decimal
    import json
    
    class DecimalEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, Decimal):
                return float(obj)
            return super(DecimalEncoder, self).default(obj)
    from django.utils import timezone
    from datetime import datetime, timedelta
    import json
    
    # 获取筛选参数
    date_range = request.GET.get('date_range', '30')
    warehouse_filter = request.GET.get('warehouse')
    category_filter = request.GET.get('category')
    
    # 计算时间范围
    end_date = timezone.now()
    if date_range == '7':
        start_date = end_date - timedelta(days=7)
    elif date_range == '90':
        start_date = end_date - timedelta(days=90)
    elif date_range == '365':
        start_date = end_date - timedelta(days=365)
    else:
        start_date = end_date - timedelta(days=30)
    
    # 耗材统计
    consumables = Consumable.objects.all()
    if warehouse_filter:
        consumables = consumables.filter(warehouse_id=warehouse_filter)
    if category_filter:
        consumables = consumables.filter(category_id=category_filter)
    
    total_consumables = consumables.count()
    total_value = consumables.aggregate(
        total=Sum(F('quantity') * F('purchase_price'))
    )['total'] or 0
    
    # 库存状态分析
    normal_stock = 0
    low_stock = 0
    out_of_stock = 0
    
    for consumable in consumables:
        if consumable.quantity <= 0:
            out_of_stock += 1
        elif consumable.quantity <= consumable.min_quantity:
            low_stock += 1
        else:
            normal_stock += 1
    
    # 按仓库分组
    warehouse_stats = consumables.values('warehouse__name').annotate(
        count=Count('id'),
        total_quantity=Sum('quantity'),
        total_value=Sum(F('quantity') * F('purchase_price'))
    ).order_by('-count')
    
    # 按类别分组
    category_stats = consumables.values('category__name').annotate(
        count=Count('id'),
        total_quantity=Sum('quantity'),
        total_value=Sum(F('quantity') * F('purchase_price'))
    ).order_by('-count')
    
    # 库存变动记录分析
    inventory_logs = InventoryLog.objects.filter(
        created_at__gte=start_date
    )
    
    if warehouse_filter:
        inventory_logs = inventory_logs.filter(consumable__warehouse_id=warehouse_filter)
    if category_filter:
        inventory_logs = inventory_logs.filter(consumable__category_id=category_filter)
    
    # 按操作类型分组
    operation_stats = inventory_logs.values('transaction_type').annotate(
        count=Count('id'),
        total_quantity=Sum('quantity')
    )
    
    # 按月份分组的库存变动 - 数据库兼容方式
    monthly_inventory = inventory_logs.extra(
        select={'month': get_date_format_sql('created_at', 'month')}
    ).values('month').annotate(
        in_count=Count('id', filter=Q(transaction_type='in')),
        out_count=Count('id', filter=Q(transaction_type='out')),
        in_quantity=Sum('quantity', filter=Q(transaction_type='in')),
        out_quantity=Sum('quantity', filter=Q(transaction_type='out'))
    ).order_by('month')
    
    # 准备图表数据
    chart_data = {
        'stock_status': [
            {'name': '正常库存', 'value': normal_stock},
            {'name': '低库存', 'value': low_stock},
            {'name': '缺货', 'value': out_of_stock}
        ],
        'warehouse_stats': list(warehouse_stats),
        'category_stats': list(category_stats),
        'operation_stats': list(operation_stats),
        'monthly_inventory': list(monthly_inventory),
    }
    
    # 获取筛选选项
    warehouses = Warehouse.objects.all().order_by('name')
    categories = AssetCategory.objects.all().order_by('name')
    
    context = {
        'total_consumables': total_consumables,
        'total_value': total_value,
        'normal_stock': normal_stock,
        'low_stock': low_stock,
        'out_of_stock': out_of_stock,
        'warehouse_stats': warehouse_stats,
        'category_stats': category_stats,
        'operation_stats': operation_stats,
        'monthly_inventory': monthly_inventory,
        'chart_data': json.dumps(chart_data, cls=DecimalEncoder),
        'warehouses': warehouses,
        'categories': categories,
        'selected_date_range': date_range,
        'selected_warehouse': warehouse_filter,
        'selected_category': category_filter,
        'date_range_options': [
            ('7', '最近7天'),
            ('30', '最近30天'),
            ('90', '最近90天'),
            ('365', '最近一年'),
        ]
    }
    
    return render(request, 'assets/inventory_analytics.html', context)


@login_required
def export_analytics_data(request):
    """导出分析数据"""
    import openpyxl
    from openpyxl.styles import Font, Alignment, PatternFill
    from django.http import HttpResponse
    from django.db.models import Count, Sum, Avg
    from django.utils import timezone
    from datetime import datetime, timedelta
    
    export_type = request.GET.get('type', 'assets')
    date_range = request.GET.get('date_range', '30')
    
    # 计算时间范围
    end_date = timezone.now()
    if date_range == '7':
        start_date = end_date - timedelta(days=7)
    elif date_range == '90':
        start_date = end_date - timedelta(days=90)
    elif date_range == '365':
        start_date = end_date - timedelta(days=365)
    else:
        start_date = end_date - timedelta(days=30)
    
    # 创建工作簿
    wb = openpyxl.Workbook()
    ws = wb.active
    
    # 设置样式
    header_font = Font(bold=True, color="FFFFFF")
    header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
    header_alignment = Alignment(horizontal="center", vertical="center")
    
    if export_type == 'assets':
        ws.title = "资产分析报表"
        
        # 设置表头
        headers = ['资产编号', '资产名称', '类别', '部门', '状态', '采购价格', '采购日期', '供应商']
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row=1, column=col, value=header)
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
        
        # 填充数据
        assets = Asset.objects.select_related('category', 'department', 'supplier', 'user').all()
        for row, asset in enumerate(assets, 2):
            ws.cell(row=row, column=1, value=asset.asset_number)
            ws.cell(row=row, column=2, value=asset.name)
            ws.cell(row=row, column=3, value=asset.category.name)
            ws.cell(row=row, column=4, value=asset.department.name if asset.department else '')
            ws.cell(row=row, column=5, value=asset.get_status_display())
            ws.cell(row=row, column=6, value=float(asset.purchase_price))
            ws.cell(row=row, column=7, value=asset.purchase_date.strftime('%Y-%m-%d'))
            ws.cell(row=row, column=8, value=asset.supplier.name if asset.supplier else '')
    
    elif export_type == 'maintenance':
        ws.title = "维修分析报表"
        
        # 设置表头
        headers = ['资产名称', '维修类型', '优先级', '状态', '报告时间', '预估成本', '实际成本', '技师']
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row=1, column=col, value=header)
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
        
        # 填充数据
        maintenance_records = MaintenanceRecord.objects.filter(
            created_at__gte=start_date
        ).select_related('asset', 'technician')
        
        for row, record in enumerate(maintenance_records, 2):
            ws.cell(row=row, column=1, value=record.asset.name)
            ws.cell(row=row, column=2, value=record.get_maintenance_type_display())
            ws.cell(row=row, column=3, value=record.get_priority_display())
            ws.cell(row=row, column=4, value=record.get_status_display())
            ws.cell(row=row, column=5, value=record.reported_at.strftime('%Y-%m-%d %H:%M'))
            ws.cell(row=row, column=6, value=float(record.estimated_cost) if record.estimated_cost else 0)
            ws.cell(row=row, column=7, value=float(record.actual_cost) if record.actual_cost else 0)
            ws.cell(row=row, column=8, value=record.technician.username if record.technician else '')
    
    elif export_type == 'inventory':
        ws.title = "库存分析报表"
        
        # 设置表头
        headers = ['耗材编号', '耗材名称', '类别', '仓库', '当前库存', '最小库存', '单价', '库存价值', '供应商']
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row=1, column=col, value=header)
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
        
        # 填充数据
        consumables = Consumable.objects.select_related('category', 'warehouse', 'supplier').all()
        for row, consumable in enumerate(consumables, 2):
            ws.cell(row=row, column=1, value=consumable.code)
            ws.cell(row=row, column=2, value=consumable.name)
            ws.cell(row=row, column=3, value=consumable.category.name)
            ws.cell(row=row, column=4, value=consumable.warehouse.name)
            ws.cell(row=row, column=5, value=float(consumable.quantity))
            ws.cell(row=row, column=6, value=float(consumable.min_quantity))
            ws.cell(row=row, column=7, value=float(consumable.purchase_price))
            ws.cell(row=row, column=8, value=float(consumable.quantity * consumable.purchase_price))
            ws.cell(row=row, column=9, value=consumable.supplier.name if consumable.supplier else '')
    
    # 调整列宽
    for column in ws.columns:
        max_length = 0
        column_letter = column[0].column_letter
        for cell in column:
            try:
                if len(str(cell.value)) > max_length:
                    max_length = len(str(cell.value))
            except:
                pass
        adjusted_width = min(max_length + 2, 50)
        ws.column_dimensions[column_letter].width = adjusted_width
    
    # 准备响应
    response = HttpResponse(
        content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    )
    filename = f'{export_type}_analytics_{timezone.now().strftime("%Y%m%d_%H%M%S")}.xlsx'
    response['Content-Disposition'] = f'attachment; filename="{filename}"'
    
    wb.save(response)
    return response


# ==================== 资产管理增强功能 ====================

@login_required
def asset_qr_code(request, asset_id):
    """生成或重新生成资产二维码"""
    asset = get_object_or_404(Asset, id=asset_id)
    
    try:
        # 生成二维码
        qr_code = asset.generate_qr_code()
        asset.save(update_fields=['qr_code'])
        
        messages.success(request, f'资产 {asset.asset_number} 的二维码已生成成功！')
    except Exception as e:
        messages.error(request, f'二维码生成失败：{str(e)}')
    
    return redirect('assets:asset_detail', pk=asset.id)


@login_required
def asset_qr_code_view(request, asset_id):
    """查看资产二维码"""
    asset = get_object_or_404(Asset, id=asset_id)
    
    # 如果没有二维码，自动生成
    if not asset.qr_code:
        try:
            asset.generate_qr_code()
            asset.save(update_fields=['qr_code'])
        except Exception as e:
            messages.error(request, f'二维码生成失败：{str(e)}')
            return redirect('assets:asset_detail', pk=asset.id)
    
    context = {
        'asset': asset,
    }
    return render(request, 'assets/asset_qr_code.html', context)


@login_required
def asset_scan_qr(request):
    """扫描二维码查询资产"""
    if request.method == 'POST':
        qr_data = request.POST.get('qr_data')
        if qr_data:
            try:
                import json
                data = json.loads(qr_data)
                asset_number = data.get('asset_number')
                
                if asset_number:
                    asset = Asset.objects.get(asset_number=asset_number)
                    return redirect('assets:asset_detail', pk=asset.id)
                else:
                    messages.error(request, '二维码数据格式错误')
            except (json.JSONDecodeError, Asset.DoesNotExist):
                messages.error(request, '无效的二维码或资产不存在')
            except Exception as e:
                messages.error(request, f'扫描失败：{str(e)}')
    
    return render(request, 'assets/asset_scan_qr.html')


# 重复的asset_upload_image函数已删除，使用第一个版本





@login_required
def asset_print_label(request, asset_id):
    """打印单个资产标签 - 增强版本"""
    asset = get_object_or_404(Asset, id=asset_id)
    
    # 获取打印参数（可以通过GET参数传递）
    label_size = request.GET.get('size', 'medium')
    include_qr = request.GET.get('qr', 'true').lower() == 'true'
    
    # 确保资产有二维码（如果需要包含二维码）
    if include_qr and not asset.qr_code:
        try:
            asset.generate_qr_code()
            asset.save(update_fields=['qr_code'])
        except Exception as e:
            messages.error(request, f'二维码生成失败：{str(e)}')
            return redirect('assets:asset_detail', pk=asset.id)
    
    # 记录打印日志
    AssetLog.log_action(
        asset=asset,
        action='label_print',
        operator=request.user,
        description=f'打印单个资产标签 - 尺寸: {label_size}, 包含二维码: {"是" if include_qr else "否"}',
        request=request
    )
    
    try:
        return generate_asset_label_pdf([asset], label_size, 1, include_qr)
    except Exception as e:
        messages.error(request, f'标签生成失败：{str(e)}')
        return redirect('assets:asset_detail', pk=asset.id)


@login_required
def asset_batch_print_labels(request):
    """批量打印资产标签 - 增强版本"""
    if request.method == 'POST':
        # 获取选中的资产ID
        selected_assets = request.POST.get('selected_assets', '')
        asset_ids = [id.strip() for id in selected_assets.split(',') if id.strip()]
        
        if not asset_ids:
            messages.error(request, '请选择要打印标签的资产')
            return redirect('assets:asset_batch_print_labels')
        
        # 获取打印选项
        label_size = request.POST.get('label_size', 'medium')
        labels_per_page = int(request.POST.get('labels_per_page', 12))
        include_qr = request.POST.get('include_qr') == 'on'
        
        # 获取资产对象
        assets = Asset.objects.filter(id__in=asset_ids).select_related('category', 'department', 'user')
        
        if not assets.exists():
            messages.error(request, '未找到选中的资产')
            return redirect('assets:asset_batch_print_labels')
        
        # 为没有二维码的资产生成二维码（如果需要包含二维码）
        if include_qr:
            for asset in assets:
                if not asset.qr_code:
                    try:
                        asset.generate_qr_code()
                        asset.save(update_fields=['qr_code'])
                    except Exception as e:
                        messages.warning(request, f'资产 {asset.asset_number} 二维码生成失败：{str(e)}')
        
        # 记录打印日志
        for asset in assets:
            AssetLog.log_action(
                asset=asset,
                action='label_print',
                operator=request.user,
                description=f'打印资产标签 - 尺寸: {label_size}, 包含二维码: {"是" if include_qr else "否"}',
                request=request
            )
        
        # 生成PDF
        try:
            return generate_asset_label_pdf(assets, label_size, labels_per_page, include_qr)
        except Exception as e:
            messages.error(request, f'标签生成失败：{str(e)}')
            return redirect('assets:asset_batch_print_labels')
    
    # GET请求 - 显示选择页面
    # 获取筛选参数
    search_query = request.GET.get('search', '')
    category_filter = request.GET.get('category', '')
    department_filter = request.GET.get('department', '')
    status_filter = request.GET.get('status', '')
    
    # 构建查询
    assets = Asset.objects.select_related('category', 'department', 'user').all()
    
    # 应用筛选条件
    if search_query:
        assets = assets.filter(
            Q(name__icontains=search_query) | 
            Q(asset_number__icontains=search_query)
        )
    
    if category_filter:
        assets = assets.filter(category_id=category_filter)
    
    if department_filter:
        assets = assets.filter(department_id=department_filter)
    
    if status_filter:
        assets = assets.filter(status=status_filter)
    
    # 排序
    assets = assets.order_by('asset_number')
    
    # 获取筛选选项
    categories = AssetCategory.objects.all().order_by('name')
    departments = Department.objects.all().order_by('name')
    
    context = {
        'assets': assets,
        'categories': categories,
        'departments': departments,
        'search_query': search_query,
        'category_filter': category_filter,
        'department_filter': department_filter,
        'status_filter': status_filter,
    }
    return render(request, 'assets/asset_batch_print_labels.html', context)


def generate_asset_label_pdf(assets, label_size='medium', labels_per_page=12, include_qr=True):
    """生成资产标签PDF - 增强版本"""
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.units import mm
    from reportlab.pdfgen import canvas
    from reportlab.lib.styles import getSampleStyleSheet
    from reportlab.platypus import Paragraph
    from reportlab.lib.colors import black, darkgrey
    from django.http import HttpResponse
    from django.conf import settings
    import os
    from datetime import datetime
    from reportlab.pdfbase import pdfmetrics
    from reportlab.pdfbase.ttfonts import TTFont
    
    # 创建HTTP响应
    response = HttpResponse(content_type='application/pdf')
    filename = f'asset_labels_{timezone.now().strftime("%Y%m%d_%H%M%S")}.pdf'
    response['Content-Disposition'] = f'attachment; filename="{filename}"'
    
    # 创建PDF
    p = canvas.Canvas(response, pagesize=A4)
    width, height = A4
    
    # 根据标签尺寸设置参数
    label_configs = {
        'small': {
            'width': 50 * mm, 'height': 25 * mm,
            'font_title': 8, 'font_content': 6, 'font_small': 5,
            'qr_size': 18 * mm, 'margin': 8 * mm
        },
        'medium': {
            'width': 70 * mm, 'height': 40 * mm,
            'font_title': 10, 'font_content': 8, 'font_small': 6,
            'qr_size': 30 * mm, 'margin': 10 * mm
        },
        'large': {
            'width': 90 * mm, 'height': 50 * mm,
            'font_title': 12, 'font_content': 10, 'font_small': 8,
            'qr_size': 35 * mm, 'margin': 12 * mm
        }
    }
    
    config = label_configs.get(label_size, label_configs['medium'])
    label_width = config['width']
    label_height = config['height']
    margin = config['margin']
    
    # 根据每页标签数计算布局
    if labels_per_page == 6:
        cols, rows = 2, 3
    elif labels_per_page == 12:
        cols, rows = 3, 4
    elif labels_per_page == 24:
        cols, rows = 4, 6
    else:
        cols, rows = 3, 4  # 默认
    
    # 计算实际可用的标签数量（基于页面大小）
    max_cols = int((width - 2 * margin) // (label_width + margin))
    max_rows = int((height - 2 * margin) // (label_height + margin))
    cols = min(cols, max_cols)
    rows = min(rows, max_rows)
    labels_per_page = cols * rows
    
    # 居中布局计算
    total_width = cols * label_width + (cols - 1) * margin
    total_height = rows * label_height + (rows - 1) * margin
    start_x = (width - total_width) / 2
    start_y = (height - total_height) / 2
    
    for i, asset in enumerate(assets):
        # 计算当前标签位置
        label_index = i % labels_per_page
        
        if label_index == 0 and i > 0:
            p.showPage()  # 新页面
        
        # 计算标签坐标
        col = label_index % cols
        row = label_index // cols
        
        x = start_x + col * (label_width + margin)
        y = start_y + (rows - row - 1) * (label_height + margin)
        
        # 绘制标签边框和背景
        p.setStrokeColor(darkgrey)
        p.setLineWidth(0.5)
        p.rect(x, y, label_width, label_height)
        
        # 绘制顶部装饰条
        p.setFillColor(darkgrey)
        p.rect(x, y + label_height - 3, label_width, 3, fill=1, stroke=0)
        
        # 标签内容区域
        content_x = x + 5
        content_y = y + label_height - 15
        content_width = label_width - 10
        
        # 如果包含二维码，调整文本区域
        if include_qr and asset.qr_code:
            content_width = label_width - config['qr_size'] - 15
        
        # 资产名称（粗体，可能需要换行）
        p.setFillColor(black)
        p.setFont("Helvetica-Bold", config['font_title'])
        
        # 处理长标题的换行
        asset_name = asset.name
        if len(asset_name) > 15:
            # 简单的换行处理
            lines = []
            words = asset_name.split()
            current_line = ""
            for word in words:
                if len(current_line + word) <= 15:
                    current_line += word + " "
                else:
                    if current_line:
                        lines.append(current_line.strip())
                    current_line = word + " "
            if current_line:
                lines.append(current_line.strip())
            
            for j, line in enumerate(lines[:2]):  # 最多显示2行
                p.drawString(content_x, content_y - j * 12, line)
            content_y -= len(lines[:2]) * 12
        else:
            p.drawString(content_x, content_y, asset_name)
            content_y -= 12
        
        # 资产编号
        p.setFont("Helvetica-Bold", config['font_content'])
        content_y -= 3
        p.drawString(content_x, content_y, f"编号: {asset.asset_number}")
        
        # 类别
        p.setFont("Helvetica", config['font_small'])
        content_y -= 10
        category_name = asset.category.name if asset.category else '未分类'
        p.drawString(content_x, content_y, f"类别: {category_name[:12]}")
        
        # 部门
        content_y -= 8
        dept_name = asset.department.name if asset.department else '未分配'
        p.drawString(content_x, content_y, f"部门: {dept_name[:12]}")
        
        # 使用人（如果有）
        if asset.user:
            content_y -= 8
            user_name = asset.user.get_full_name() or asset.user.username
            p.drawString(content_x, content_y, f"使用人: {user_name[:10]}")
        
        # 采购日期
        content_y -= 8
        purchase_date = asset.purchase_date.strftime('%Y-%m-%d') if asset.purchase_date else '未知'
        p.drawString(content_x, content_y, f"采购: {purchase_date}")
        
        # 二维码（如果存在且需要包含）
        if include_qr and asset.qr_code:
            try:
                qr_path = os.path.join(settings.MEDIA_ROOT, asset.qr_code.name)
                if os.path.exists(qr_path):
                    # 二维码位置（右侧）
                    qr_size = config['qr_size']
                    qr_x = x + label_width - qr_size - 5
                    qr_y = y + (label_height - qr_size) / 2
                    p.drawImage(qr_path, qr_x, qr_y, width=qr_size, height=qr_size)
                    
                    # 二维码下方添加小字提示
                    p.setFont("Helvetica", 4)
                    p.drawString(qr_x + qr_size/2 - 10, qr_y - 8, "扫码查看详情")
            except Exception as e:
                # 如果二维码加载失败，显示文本提示
                p.setFont("Helvetica", config['font_small'])
                p.drawString(x + label_width - 30, y + 10, "无二维码")
        
        # 在标签底部添加打印时间戳
        p.setFont("Helvetica", 4)
        p.setFillColor(darkgrey)
        timestamp = timezone.now().strftime('%Y-%m-%d %H:%M')
        p.drawString(x + 2, y + 2, f"打印: {timestamp}")
    
    # 添加页脚信息
    p.setFont("Helvetica", 8)
    p.setFillColor(darkgrey)
    footer_text = f"资产标签 - 共{len(assets)}个资产 - 生成时间: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}"
    p.drawString(50, 30, footer_text)
    
    p.save()
    return response

@login_required
def asset_print_label_config(request, asset_id=None):
    """标签打印配置页面"""
    asset = None
    asset_ids = None
    asset_count = 1
    
    if asset_id:
        # 单个资产打印配置
        asset = get_object_or_404(Asset, id=asset_id)
    else:
        # 批量打印配置
        asset_ids = request.GET.get('asset_ids', '')
        if asset_ids:
            asset_id_list = [id.strip() for id in asset_ids.split(',') if id.strip()]
            asset_count = len(asset_id_list)
        else:
            messages.error(request, '未指定要打印的资产')
            return redirect('assets:asset_list')
    
    if request.method == 'POST':
        # 处理配置并生成标签
        label_size = request.POST.get('label_size', 'medium')
        labels_per_page = int(request.POST.get('labels_per_page', 12))
        include_qr = request.POST.get('include_qr') == 'on'
        include_category = request.POST.get('include_category') == 'on'
        include_department = request.POST.get('include_department') == 'on'
        include_user = request.POST.get('include_user') == 'on'
        include_date = request.POST.get('include_date') == 'on'
        include_timestamp = request.POST.get('include_timestamp') == 'on'
        title_prefix = request.POST.get('title_prefix', '')
        footer_note = request.POST.get('footer_note', '')
        
        # 获取资产
        if asset:
            assets = [asset]
        else:
            asset_id_list = [id.strip() for id in asset_ids.split(',') if id.strip()]
            assets = Asset.objects.filter(id__in=asset_id_list).select_related('category', 'department', 'user')
        
        # 生成标签配置
        label_config = {
            'size': label_size,
            'labels_per_page': labels_per_page,
            'include_qr': include_qr,
            'include_category': include_category,
            'include_department': include_department,
            'include_user': include_user,
            'include_date': include_date,
            'include_timestamp': include_timestamp,
            'title_prefix': title_prefix,
            'footer_note': footer_note,
        }
        
        # 为需要二维码的资产生成二维码
        if include_qr:
            for asset_obj in assets:
                if not asset_obj.qr_code:
                    try:
                        asset_obj.generate_qr_code()
                        asset_obj.save(update_fields=['qr_code'])
                    except Exception as e:
                        messages.warning(request, f'资产 {asset_obj.asset_number} 二维码生成失败：{str(e)}')
        
        # 记录打印日志
        for asset_obj in assets:
            AssetLog.log_action(
                asset=asset_obj,
                action='label_print',
                operator=request.user,
                description=f'配置打印标签 - 尺寸: {label_size}, 配置: {label_config}',
                request=request
            )
        
        try:
            return generate_enhanced_asset_label_pdf(assets, label_config)
        except Exception as e:
            messages.error(request, f'标签生成失败：{str(e)}')
            return redirect('assets:asset_list')
    
    # GET请求 - 显示配置页面
    from .models_label import LabelTemplate
    
    # 获取用户的模板
    user_templates = LabelTemplate.objects.filter(
        Q(created_by=request.user) | Q(is_public=True)
    ).order_by('name')
    
    context = {
        'asset': asset,
        'asset_ids': asset_ids,
        'asset_count': asset_count,
        'user_templates': user_templates,
    }
    return render(request, 'assets/asset_print_label_config.html', context)


def generate_enhanced_asset_label_pdf(assets, config):
    """生成增强版资产标签PDF"""
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.units import mm
    from reportlab.pdfgen import canvas
    from reportlab.lib.colors import black, darkgrey, lightgrey
    from django.http import HttpResponse
    from django.conf import settings
    import os
    from datetime import datetime
    
    # 创建HTTP响应
    response = HttpResponse(content_type='application/pdf')
    filename = f'asset_labels_{timezone.now().strftime("%Y%m%d_%H%M%S")}.pdf'
    response['Content-Disposition'] = f'attachment; filename="{filename}"'
    
    # 创建PDF
    p = canvas.Canvas(response, pagesize=A4)
    width, height = A4
    
    # 根据标签尺寸设置参数
    label_configs = {
        'small': {
            'width': 50 * mm, 'height': 25 * mm,
            'font_title': 8, 'font_content': 6, 'font_small': 5,
            'qr_size': 18 * mm, 'margin': 8 * mm
        },
        'medium': {
            'width': 70 * mm, 'height': 40 * mm,
            'font_title': 10, 'font_content': 8, 'font_small': 6,
            'qr_size': 30 * mm, 'margin': 10 * mm
        },
        'large': {
            'width': 90 * mm, 'height': 50 * mm,
            'font_title': 12, 'font_content': 10, 'font_small': 8,
            'qr_size': 35 * mm, 'margin': 12 * mm
        }
    }
    
    size_config = label_configs.get(config['size'], label_configs['medium'])
    label_width = size_config['width']
    label_height = size_config['height']
    margin = size_config['margin']
    
    # 根据每页标签数计算布局
    labels_per_page = config['labels_per_page']
    if labels_per_page == 6:
        cols, rows = 2, 3
    elif labels_per_page == 12:
        cols, rows = 3, 4
    elif labels_per_page == 24:
        cols, rows = 4, 6
    else:
        cols, rows = 3, 4  # 默认
    
    # 计算实际可用的标签数量（基于页面大小）
    max_cols = int((width - 2 * margin) // (label_width + margin))
    max_rows = int((height - 2 * margin) // (label_height + margin))
    cols = min(cols, max_cols)
    rows = min(rows, max_rows)
    labels_per_page = cols * rows
    
    # 居中布局计算
    total_width = cols * label_width + (cols - 1) * margin
    total_height = rows * label_height + (rows - 1) * margin
    start_x = (width - total_width) / 2
    start_y = (height - total_height) / 2
    
    for i, asset in enumerate(assets):
        # 计算当前标签位置
        label_index = i % labels_per_page
        
        if label_index == 0 and i > 0:
            p.showPage()  # 新页面
        
        # 计算标签坐标
        col = label_index % cols
        row = label_index // cols
        
        x = start_x + col * (label_width + margin)
        y = start_y + (rows - row - 1) * (label_height + margin)
        
        # 绘制标签边框和背景
        p.setStrokeColor(darkgrey)
        p.setLineWidth(0.5)
        p.rect(x, y, label_width, label_height)
        
        # 绘制顶部装饰条
        p.setFillColor(lightgrey)
        p.rect(x, y + label_height - 3, label_width, 3, fill=1, stroke=0)
        
        # 标签内容区域
        content_x = x + 5
        content_y = y + label_height - 15
        content_width = label_width - 10
        
        # 如果包含二维码，调整文本区域
        if config['include_qr'] and asset.qr_code:
            content_width = label_width - size_config['qr_size'] - 15
        
        # 标题前缀
        p.setFillColor(black)
        if config.get('title_prefix'):
            p.setFont("Helvetica", size_config['font_small'])
            p.drawString(content_x, content_y, config['title_prefix'])
            content_y -= 8
        
        # 资产名称（粗体）
        p.setFont("Helvetica-Bold", size_config['font_title'])
        asset_name = asset.name
        if len(asset_name) > 15:
            # 简单的换行处理
            lines = []
            words = asset_name.split()
            current_line = ""
            for word in words:
                if len(current_line + word) <= 15:
                    current_line += word + " "
                else:
                    if current_line:
                        lines.append(current_line.strip())
                    current_line = word + " "
            if current_line:
                lines.append(current_line.strip())
            
            for j, line in enumerate(lines[:2]):  # 最多显示2行
                p.drawString(content_x, content_y - j * 10, line)
            content_y -= len(lines[:2]) * 10
        else:
            p.drawString(content_x, content_y, asset_name)
            content_y -= 10
        
        # 资产编号
        p.setFont("Helvetica-Bold", size_config['font_content'])
        content_y -= 2
        p.drawString(content_x, content_y, f"编号: {asset.asset_number}")
        content_y -= 8
        
        # 可选内容
        p.setFont("Helvetica", size_config['font_small'])
        
        if config['include_category']:
            category_name = asset.category.name if asset.category else '未分类'
            p.drawString(content_x, content_y, f"类别: {category_name[:12]}")
            content_y -= 7
        
        if config['include_department']:
            dept_name = asset.department.name if asset.department else '未分配'
            p.drawString(content_x, content_y, f"部门: {dept_name[:12]}")
            content_y -= 7
        
        if config['include_user'] and asset.user:
            user_name = asset.user.get_full_name() or asset.user.username
            p.drawString(content_x, content_y, f"使用人: {user_name[:10]}")
            content_y -= 7
        
        if config['include_date'] and asset.purchase_date:
            purchase_date = asset.purchase_date.strftime('%Y-%m-%d')
            p.drawString(content_x, content_y, f"采购: {purchase_date}")
            content_y -= 7
        
        # 二维码（如果存在且需要包含）
        if config['include_qr'] and asset.qr_code:
            try:
                qr_path = os.path.join(settings.MEDIA_ROOT, asset.qr_code.name)
                if os.path.exists(qr_path):
                    # 二维码位置（右侧）
                    qr_size = size_config['qr_size']
                    qr_x = x + label_width - qr_size - 5
                    qr_y = y + (label_height - qr_size) / 2
                    p.drawImage(qr_path, qr_x, qr_y, width=qr_size, height=qr_size)
                    
                    # 二维码下方添加小字提示
                    p.setFont("Helvetica", 4)
                    p.drawString(qr_x + qr_size/2 - 10, qr_y - 8, "扫码查看详情")
            except Exception as e:
                # 如果二维码加载失败，显示文本提示
                p.setFont("Helvetica", size_config['font_small'])
                p.drawString(x + label_width - 30, y + 10, "无二维码")
        
        # 底部信息
        if config['include_timestamp']:
            p.setFont("Helvetica", 4)
            p.setFillColor(darkgrey)
            timestamp = timezone.now().strftime('%Y-%m-%d %H:%M')
            p.drawString(x + 2, y + 2, f"打印: {timestamp}")
        
        # 底部备注
        if config.get('footer_note'):
            p.setFont("Helvetica", 4)
            p.setFillColor(darkgrey)
            p.drawString(x + 2, y + 8, config['footer_note'][:25])
    
    # 添加页脚信息
    p.setFont("Helvetica", 8)
    p.setFillColor(darkgrey)
    footer_text = f"资产标签 - 共{len(assets)}个资产 - 生成时间: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}"
    p.drawString(50, 30, footer_text)
    
    p.save()
    return response


@login_required
def mobile_asset_scan(request):
    """移动端资产扫码页面"""
    return render(request, 'assets/mobile/asset_scan.html')


# 合同文件上传相关视图
@login_required
@require_POST
def contract_upload_file(request, pk):
    """合同文件上传视图"""
    from django.http import JsonResponse
    from .models import Contract, ContractFile
    import os
    
    try:
        contract = get_object_or_404(Contract, pk=pk)
        
        # 兼容字段名：同时支持 'file' 与历史的 'file_path'
        uploaded_file = request.FILES.get('file') or request.FILES.get('file_path')
        if not uploaded_file:
            return JsonResponse({'success': False, 'error': '请选择要上传的文件'})
        
        file_type = request.POST.get('file_type', 'attachment')
        description = request.POST.get('description', '')
        override_name = (request.POST.get('file_name') or '').strip()
        
        # 检查文件大小（限制为50MB）
        if uploaded_file.size > 50 * 1024 * 1024:
            return JsonResponse({'success': False, 'error': '文件大小不能超过50MB'})
        
        # 检查文件类型
        allowed_extensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.jpg', '.jpeg', '.png', '.txt']
        file_extension = os.path.splitext(uploaded_file.name)[1].lower()
        if file_extension not in allowed_extensions:
            return JsonResponse({'success': False, 'error': '不支持的文件类型'})
        
        # 创建合同文件记录
        contract_file = ContractFile.objects.create(
            contract=contract,
            file_type=file_type,
            file_name=override_name or uploaded_file.name,
            file_path=uploaded_file,
            file_size=uploaded_file.size,
            description=description,
            uploaded_by=request.user
        )
        
        return JsonResponse({
            'success': True,
            'message': '文件上传成功',
            'file': {
                'id': contract_file.id,
                'name': contract_file.file_name,
                'type': contract_file.get_file_type_display(),
                'size': contract_file.file_size_mb,
                'uploaded_at': contract_file.uploaded_at.strftime('%Y-%m-%d %H:%M'),
                'uploaded_by': contract_file.uploaded_by.username if contract_file.uploaded_by else '',
                'url': contract_file.file_path.url if contract_file.file_path else ''
            }
        })
        
    except Exception as e:
        return JsonResponse({'success': False, 'error': f'上传失败: {str(e)}'})


@login_required
@require_POST
def contract_delete_file(request, file_id):
    """删除合同文件视图"""
    from django.http import JsonResponse
    from .models import ContractFile
    
    try:
        contract_file = get_object_or_404(ContractFile, pk=file_id)
        
        # 检查权限（只有上传者或超级用户可以删除）
        if not (request.user.is_superuser or contract_file.uploaded_by == request.user):
            return JsonResponse({'success': False, 'error': '您没有权限删除此文件'})
        
        file_name = contract_file.file_name
        contract_file.delete()
        
        return JsonResponse({
            'success': True,
            'message': f'文件 "{file_name}" 删除成功'
        })
        
    except Exception as e:
        return JsonResponse({'success': False, 'error': f'删除失败: {str(e)}'})


# 供应商资质文件上传相关视图
@login_required
@require_POST
def supplier_qualification_upload(request):
    """供应商资质文件上传视图"""
    from django.http import JsonResponse
    from .models import Supplier, SupplierDocument
    import os
    
    try:
        supplier_id = request.POST.get('supplier_id')
        if not supplier_id:
            return JsonResponse({'success': False, 'error': '缺少供应商ID'})
        
        supplier = get_object_or_404(Supplier, pk=supplier_id)
        
        if 'file' not in request.FILES:
            return JsonResponse({'success': False, 'error': '请选择要上传的文件'})
        
        uploaded_file = request.FILES['file']
        document_type = request.POST.get('document_type', 'qualification')
        title = request.POST.get('title', uploaded_file.name)
        description = request.POST.get('description', '')
        expiry_date = request.POST.get('expiry_date')
        
        # 检查文件大小（限制为50MB）
        if uploaded_file.size > 50 * 1024 * 1024:
            return JsonResponse({'success': False, 'error': '文件大小不能超过50MB'})
        
        # 检查文件类型
        allowed_extensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.jpg', '.jpeg', '.png', '.txt']
        file_extension = os.path.splitext(uploaded_file.name)[1].lower()
        if file_extension not in allowed_extensions:
            return JsonResponse({'success': False, 'error': '不支持的文件类型'})
        
        # 创建供应商文档记录
        supplier_doc = SupplierDocument.objects.create(
            supplier=supplier,
            document_type=document_type,
            title=title,
            file=uploaded_file,
            description=description,
            uploaded_by=request.user
        )
        
        # 处理有效期
        if expiry_date:
            try:
                from datetime import datetime
                supplier_doc.expiry_date = datetime.strptime(expiry_date, '%Y-%m-%d').date()
                supplier_doc.save()
            except ValueError:
                pass  # 忽略无效的日期格式
        
        return JsonResponse({
            'success': True,
            'message': '资质文件上传成功',
            'file': {
                'id': supplier_doc.id,
                'title': supplier_doc.title,
                'type': supplier_doc.get_document_type_display(),
                'size': supplier_doc.get_file_size(),
                'uploaded_at': supplier_doc.created_at.strftime('%Y-%m-%d %H:%M'),
                'uploaded_by': supplier_doc.uploaded_by.username if supplier_doc.uploaded_by else '',
                'url': supplier_doc.get_file_url(),
                'expiry_date': supplier_doc.expiry_date.strftime('%Y-%m-%d') if supplier_doc.expiry_date else '',
                'is_expired': supplier_doc.is_expired()
            }
        })
        
    except Exception as e:
        return JsonResponse({'success': False, 'error': f'上传失败: {str(e)}'})


@login_required
@require_POST
def supplier_qualification_delete(request, file_id):
    """删除供应商资质文件视图"""
    from django.http import JsonResponse
    from .models import SupplierDocument
    
    try:
        supplier_doc = get_object_or_404(SupplierDocument, pk=file_id)
        
        # 检查权限（只有上传者或超级用户可以删除）
        if not (request.user.is_superuser or supplier_doc.uploaded_by == request.user):
            return JsonResponse({'success': False, 'error': '您没有权限删除此文件'})
        
        file_title = supplier_doc.title
        supplier_doc.delete()
        
        return JsonResponse({
            'success': True,
            'message': f'资质文件 "{file_title}" 删除成功'
        })
        
    except Exception as e:
        return JsonResponse({'success': False, 'error': f'删除失败: {str(e)}'})


@login_required
def supplier_document_view(request, file_id):
    """在线查看供应商文档"""
    from django.http import FileResponse, Http404
    import mimetypes
    
    try:
        supplier_doc = get_object_or_404(SupplierDocument, pk=file_id)
        
        if not supplier_doc.file:
            raise Http404("文件不存在")
        
        # 获取文件路径
        file_path = supplier_doc.file.path
        
        # 检查文件是否存在
        if not os.path.exists(file_path):
            raise Http404("文件不存在")
        
        # 获取文件类型
        content_type, _ = mimetypes.guess_type(file_path)
        
        # 对于PDF文件，设置为在浏览器中显示
        if content_type == 'application/pdf':
            response = FileResponse(
                open(file_path, 'rb'),
                content_type=content_type,
                filename=supplier_doc.title
            )
            response['Content-Disposition'] = f'inline; filename="{supplier_doc.title}"'
        else:
            # 对于其他文件类型，提供下载
            response = FileResponse(
                open(file_path, 'rb'),
                content_type=content_type or 'application/octet-stream',
                filename=supplier_doc.title
            )
            response['Content-Disposition'] = f'attachment; filename="{supplier_doc.title}"'
        
        return response
        
    except Exception as e:
        raise Http404(f"无法访问文件: {str(e)}")
